SKIX Product Framework 공개 경계 검토 기록
상위 구조: 이 문서는 SKIX 제품 프레임워크 전체 중 외부 앱과 제품 기능 사이의 북향 API·신뢰 경계를 다룬다. 상태, 정책, 자원, lifecycle, Task 실행과 Domain Framework 전체 구성은 SKIX 리팩터링 프레임워크 설계안을 기준으로 본다.
현재 결정: Gateway는 별도 프로세스의 확정 구성요소가 아니다. 1차 단계에서는 DeviceAgent의 제품 Runtime Binder 진입점 안에 호출자 검증·계약 변환·결과 필터링 책임을 논리 모듈로 둔다. 별도 APK나 프로세스는 승인 파트너 공개, 독립 업데이트 또는 장애 격리가 필요할 때 검토한다.
1. 결정 요약
SKIX 제품 기능은 DeviceAgent 안에 이동, 청정, 환경, 화면, TTS, 보안, 예약, Vision, 업데이트 등으로 축적되어 있다. TaskManager는 이 기능을 Task와 Workflow 단위로 실행하고 상태, 순서, 취소, 실패, 완료 증거를 관리하는 실행 Core다.
공개 경계에는 시스템 앱과 승인된 파트너 앱이 사용할 안정된 API, 호출자 검증, 계약 변환과 결과
필터링이 필요하다. 이 문서에서 Gateway는 그 책임 묶음을 뜻한다. 1차 구현은 별도 서비스가
아니라 제품 Runtime Service 내부 모듈로 시작한다.
앱이 말하는 것: 어떤 기능을 어떤 조건으로 사용하고 싶은가
Gateway가 판단하는 것: 누가 어떤 API와 capability를 사용할 수 있는가
TaskManager가 관리하는 것: 허용된 실행을 언제, 어떤 순서로, 어떤 종료 상태까지 수행하는가
도메인이 수행하는 것: 이동, 청정, 화면, 측정과 같은 실제 기기 동작
이 설계에서 Gateway는 TaskManager를 우회하거나 대체하지 않는다. Gateway는 공개 API와 신뢰 경계, TaskManager는 실행 생명주기 경계다.
2. 현재 기반과 목표 상태
| 구분 | 확인된 기반 | 1차 목표 |
|---|---|---|
| 공통 호출 | DeviceControlProxy와 IDeviceControl.sendModuleCommand(Bundle) |
외부 앱이 raw Bundle과 문자열 메서드를 직접 다루지 않는 typed SDK |
| 제품 실행 | DeviceAgent.MainApi와 도메인 manager |
도메인 Facade가 기존 구현을 감싸고 내부 변경을 외부 계약과 분리 |
| Task 실행 | MR6 task_manager 브랜치의 TaskManager, policy, executor, completion, event |
Gateway 요청을 검증된 Task 또는 Workflow로 변환하고 terminal 결과를 반환 |
| Task client | TaskManagerClient AAR |
사내 시스템 앱과 Gateway가 사용하는 내부 실행 adapter로 제한 |
| UI | SKLauncher2의 Java, AppCompat, ViewBinding/DataBinding, XML resource와 공통 widget |
UI token, 공용 component, 시스템 surface 계약을 별도 AAR로 분리 |
| 외부 앱 보안 | 현재 체크아웃의 exported Binder와 일부 명령별 UID 검사 | 공통 서비스 권한, package·certificate 검증과 capability grant를 요청마다 적용 |
상태 표기 원칙은 다음과 같다.
| 표기 | 의미 |
|---|---|
| 현재 | 현재 체크아웃에서 직접 확인되는 API, 앱, 리소스 |
| 구현 기반 | MR6의 task_manager 브랜치에서 확인되는 TaskManager와 AAR 구현 |
| 목표 | 아직 구현되지 않은 Product Framework Gateway, 파트너 정책, UI Foundation |
현재 체크아웃과 TaskManager 구현 브랜치가 다르므로, 실제 리팩터링을 시작하기 전에 기준 브랜치를 하나로 정합하는 작업이 선행되어야 한다.
보안 기준선 주의: MR6의
task_manager브랜치에는DEVICE_CONTROLsignature permission과 service 보호가 들어가지만, 현재speaker_identification체크아웃의DeviceAgentservice에는 같은 manifest permission이 보이지 않는다. 어느 상태가 출시 기준인지 먼저 정합하고, 별도 인증서 파트너에게는 두 경우 모두 기존IDeviceControlBinder를 직접 공개하지 않는다.
3. 왜 Gateway와 TaskManager를 분리하는가
두 계층을 합치면 TaskManager가 앱 인증, API 버전, UI surface, 파트너 계약까지 소유하게 된다. 그러면 실행 Core가 제품 배포와 파트너 정책에 종속된다. 반대로 Gateway가 queue와 Workflow 실행을 다시 구현하면 실행 상태의 소유자가 두 곳이 되어 취소, timeout, 완료 판정이 갈라진다.
| 책임 | Product Framework Gateway | TaskManager Core |
|---|---|---|
| 호출자 식별 | Binder UID, package, signing certificate 확인 | 검증된 source와 principal만 수신 |
| 권한 | capability grant, 사용자 동의 요구, API별 제한 | 기기 상태, 자원, priority, queue admission |
| 계약 | public request를 내부 Task/Workflow로 변환 | Task/Workflow schema 검증과 실행 |
| 버전 | SDK/API 버전 협상, deprecated API 차단 | 내부 task contract version 처리 |
| 상태 조회 | public snapshot과 event 필터링 | task record, progress, terminal event 생성 |
| UI | 공용 UI token과 surface route 제공 | UI 전환이 Task일 때 실행과 완료만 관리 |
| 실행 | 직접 실행하지 않음 | queue, 순서, timeout, retry, cancel, compensation 관리 |
3.1 실행 API 관계
SkixFrameworkClient.submit(ActionRequest)
-> Gateway: caller identity + capability grant + API version
-> GatewayTaskAdapter: public capability/operation을 TaskSubmitRequest로 변환
-> TaskManagerClient: submitTask 또는 submitWorkflow
-> TaskManager: admission, queue, execution, completion
-> GatewayResultMapper: 내부 reason/event를 public result/event로 정규화
-> Partner app: TaskHandle, progress, terminal result
TaskManagerClient.sendRaw(Bundle)와 임의 taskMethod는 내부 호환용으로 남기고 파트너 SDK에는 노출하지 않는다. 파트너는 Gateway가 공개한 capability와 operation만 조합할 수 있다.
3.2 조회와 UI는 Task로 강제하지 않는다
모든 API를 TaskManager에 넣는 것은 과설계다.
| 요청 종류 | 권장 경로 | 이유 |
|---|---|---|
| 현재 배터리, 위치, 공기질 조회 | Gateway State API | 실행 생명주기가 없는 read-only snapshot |
| 이동, 청정, 설정 변경 | Gateway Action API -> TaskManager | 실행 허용, 취소, 완료 증거가 필요 |
| 여러 단계 기능 | Gateway Workflow API -> TaskManager | 단계 의존관계와 실패 정책이 필요 |
| Task 진행 관찰 | Gateway Event API -> TaskManager event | caller별 필터와 안정된 public event 필요 |
| 제품 화면 열기 | Gateway Surface API -> UI Surface Router | Activity와 Fragment 클래스명을 공개하지 않기 위함 |
| 테마와 공용 widget | UI Foundation AAR | 앱 프로세스 안에서 렌더링되는 정적 UI 자산 |
4. 권장 계층
4.1 Consumer Layer
| 소비자 | 접근 방식 | 허용 범위 |
|---|---|---|
| SKIX 시스템 앱 | Public SDK 우선, 필요한 내부 기능만 Internal SDK | 전체 제품 capability 중 앱 역할에 필요한 범위 |
| 승인 파트너 앱 | Partner SDK와 Gateway만 사용 | 인증서별 grant가 있는 capability |
| 미승인 일반 앱 | 직접 제어 API 없음 | 허용된 공개 Intent 또는 사용자 주도 화면만 선택적으로 제공 |
시스템 앱도 장기적으로 Public SDK를 우선 사용해야 파트너 경로와 동일한 API가 실제 제품에서 검증된다. 단, 공장 검사, 복구, emergency처럼 외부에 공개할 수 없는 기능은 Internal SDK로 분리한다.
4.2 SDK Layer
| 후보 모듈 | 책임 | 공개 대상 |
|---|---|---|
skix-framework-contract |
request, result, event, capability descriptor, reason code | 시스템 앱과 승인 파트너 |
skix-framework-client |
Gateway bind, Binder death 복구, async callback, API 협상 | 시스템 앱과 승인 파트너 |
skix-ui-tokens |
의미 기반 color, typography, spacing, shape, motion token | UI를 만드는 앱 |
skix-ui-components |
버튼, popup, progress, status, empty/error view | UI를 만드는 앱 |
skix-ui-surfaces |
제품 화면 ID와 launch argument 계약 | 화면 진입이 허용된 앱 |
skix-framework-testkit |
fake Gateway, capability fixture, Task event simulator | 파트너와 사내 개발·검증 |
taskmanagerclient |
raw TaskManager adapter와 내부 constants | DeviceAgent, Gateway, 승인된 사내 시스템 구성요소만 |
UI AAR와 실행 AAR를 분리한다. 테마 변경 때문에 기기 실행 SDK가 재배포되거나, Device API 변경 때문에 모든 UI 리소스를 다시 검증하는 결합을 피하기 위해서다.
4.3 공개 API와 신뢰 경계
1차 구현은 DeviceAgent 프로세스의 제품 Runtime Binder 진입점 안에 아래 책임을 둔다. 동일 프로세스라도 호출자 UID·인증서 검증과 공개 계약 변환은 기존 raw command 처리보다 앞에서 수행한다. 별도 인증서 파트너가 늘어나거나 독립 배포·장애 격리가 필요해지면 이 책임 묶음을 별도 privileged service로 옮길 수 있다.
| 구성요소 | 책임 |
|---|---|
| Caller Identity Resolver | Binder.getCallingUid()를 package와 signing certificate로 해석 |
| Partner Entitlement Registry | 승인 package, certificate digest, API 범위, capability grant 보관 |
| API Version Negotiator | SDK와 Framework의 지원 버전을 비교하고 호환 API 선택 |
| Capability Registry | 공개 capability, operation, 필수 parameter, 위험 등급 제공 |
| State Gateway | DeviceAgent 상태를 public snapshot으로 필터링하고 freshness 표시 |
| Task Adapter | Action/Workflow를 TaskSubmitRequest와 TaskWorkflowRequest로 변환 |
| Event Gateway | Task event를 호출자 소유권과 구독 범위에 맞게 필터링 |
| Result Mapper | 내부 reason code와 Bundle을 안정된 public result로 정규화 |
| UI Surface Router | 공개 surface ID를 SKLauncher2의 명시적 화면 계약으로 연결 |
Gateway는 요청마다 호출자 UID를 다시 검증한다. 연결 시 한 번 확인한 package 문자열이나 앱이 요청에 넣은 source 값은 신뢰하지 않는다.
4.4 Execution and Domain Layer
TaskManager는 공개 앱 API를 알 필요가 없다. Gateway가 보낸 내부 계약을 기존 방식으로 처리한다.
TaskManager Core
-> admission / policy / resource
-> queue / priority / workflow / scheduler
-> lifecycle / timeout / cancel / compensation
-> executor registry
-> completion watcher / event / reason
Executor는 도메인 Facade를 호출하고 Facade가 현재 DeviceAgent 구현과 peer agent를 감싼다.
| 도메인 Facade | 현재 연결 후보 |
|---|---|
| Mobility | moving/*, device/item/amr/*, AMRAgent |
| Cleaning | cleaning/*, AirQualityManager, MCU fan |
| Environment | 공기질, 온습도, filter, sensor 상태 |
| Interaction | TTS, Eye LED, 화면, 대화·LLM 상태 |
| Health | Vital Signs 앱 세션과 완료·취소 증거 |
| Security and LiveView | WSS, streaming, privacy, security 상태 |
| Schedule and Routine | 제품 schedule, Welcome, deferred Task |
| Vision and Perception | VisionAgent, 의미 관찰, 카메라 정책 |
| System and Maintenance | 설정, 네트워크, OTA, 오류, 부품과 사용 시간 |
Facade는 기존 기능을 복사하지 않는다. 입력 정규화, 정책 전처리, 실행 호출, 완료 증거 변환만 소유한다.
5. 파트너 공개 API 초안
아래 이름은 계약 검토를 위한 후보이며 구현 완료 API가 아니다.
SkixFrameworkClient client = SkixFramework.connect(context);
FrameworkInfo info = client.getFrameworkInfo();
List<CapabilityDescriptor> capabilities = client.getCapabilities();
DeviceSnapshot snapshot = client.getDeviceSnapshot(StateQuery.basic());
TaskHandle handle = client.submitAction(
ActionRequest.builder("mobility.move")
.requestId("partner-req-001")
.put("targetRoom", "안방")
.build()
);
TaskSubscription subscription = client.observeTask(
handle.getTaskId(),
event -> renderProgress(event)
);
Workflow는 raw taskMethod 배열이 아니라 공개 capability operation의 조합으로 받는다.
WorkflowRequest workflow = WorkflowRequest.builder("inspect-and-clean")
.addStep(ActionStep.of("mobility.move").put("targetRoom", "거실"))
.addStep(ActionStep.of("cleaning.startBasic").afterPrevious())
.addStep(ActionStep.of("mobility.returnToStation").afterPrevious())
.failurePolicy(FailurePolicy.STOP_AND_REPORT)
.build();
TaskHandle handle = client.submitWorkflow(workflow);
5.1 파트너가 설정할 수 없는 값
파트너 앱이 다음 값을 직접 결정하게 하면 내부 안전 정책을 우회할 수 있다.
| 비공개 필드 | 처리 방식 |
|---|---|
forceTaskManager |
Gateway가 항상 관리 실행으로 고정 |
raw taskMethod |
capability operation을 통해서만 내부 매핑 |
source |
Gateway가 검증된 principal에 따라 PARTNER로 설정 |
| emergency priority | 외부 요청 금지. 별도 제품 정책과 사용자 동의가 있는 API만 승격 |
bypassCallerPolicy |
외부 계약에서 제거 |
| 임의 queue, cancel method, compensation method | Capability Registry와 Task policy가 결정 |
| 내부 completion key | Gateway와 domain adapter가 상관관계 ID로 생성 |
5.2 Public 결과 모델
| 모델 | 최소 필드 |
|---|---|
TaskHandle |
requestId, taskId, acceptedAt, initialState |
TaskEvent |
taskId, eventType, progress, stage, timestamp, reason |
TaskResult |
taskId, terminalState, resultCode, reasonCode, data |
DeviceSnapshot |
stateVersion, capturedAt, freshness, 허용된 domain state |
CapabilityDescriptor |
capabilityId, operations, riskTier, requiredConsent, schemaVersion |
내부 클래스명, exception stack, MCU code, raw Bundle은 partner result에 그대로 노출하지 않는다. 운영 추적용 traceId는 허용된 범위에서만 반환한다.
6. 인증서가 다른 승인 파트너의 접근 모델
기존 signature permission 하나만으로는 별도 인증서 파트너를 안전하게 지원할 수 없다. 파트너 Gateway는 Android permission 선언만 믿지 않고 service 내부에서 호출자 신원을 검증해야 한다.
6.1 권장 검증 순서
Binder.getCallingUid()로 실제 호출 UID를 얻는다.PackageManager.getPackagesForUid()로 UID에 연결된 package를 확인한다.- 설치된 package의 signing certificate digest를 읽는다.
- 읽기 전용 Framework policy에 등록된 package와 certificate 조합을 비교한다.
- API version, capability grant, risk tier, 사용자 동의 조건을 검증한다.
- 검증된 principal 정보를 Gateway 내부에서 생성하고 Task 요청에 주입한다.
- 모든 API 호출과 Task event 구독에서 소유권을 다시 확인한다.
파트너가 전송한 package name, source, certificate 문자열은 신원 증거로 사용하지 않는다. shared UID가 있는 경우 연결된 모든 package와 signing lineage를 검증하거나 v1에서 거절한다.
6.2 권한 단계
| 단계 | 예시 | 허용 정책 |
|---|---|---|
| Internal | platform-signed SKIX 앱 | 내부 capability를 역할별로 부여 |
| Partner | 별도 인증서의 승인 앱 | package + certificate + capability allowlist |
| Public Surface Only | 제한된 일반 앱 | 명시적 사용자 동작으로 열리는 화면만 제공 |
| Denied | 미등록 앱 | bind 또는 모든 API 호출 거절, 감사 event 기록 |
인증서 교체는 signing lineage와 정책 version을 가진 별도 승인 절차로 관리한다. OTA로 policy를 갱신할 때는 rollback과 이전 SDK 호환성을 함께 검증한다.
7. SKLauncher2 UI Foundation 분리안
현재 SKLauncher2에는 공용 후보인 RoundedConstraintLayout, AirBotSwitch, AirBotSideBar, AirBotToastView, AirClearProgressBar, AlertView, typography style, color, font, animation resource가 있다. 그대로 복사해 공개하는 것이 아니라 재사용 가능성 기준으로 분류한다.
| 분류 | 추출 기준 | 예시 |
|---|---|---|
| Token | 화면이나 도메인 이름 없이 의미로 표현 가능 | colorStatusError, spacePanel, motionProgress |
| Primitive Component | DeviceAgent 호출과 Activity 생명주기를 소유하지 않음 | switch, option group, status view, progress view |
| Pattern Component | 일관된 확인, 오류, 진행 UX를 제공 | task progress, cancel confirmation, unavailable state |
| Product Surface | 제품 기능과 내비게이션을 소유 | 청정, 이동, 바이탈, 설정 화면 |
| App Internal | 특정 Fragment, repository, 화면 전용 binding에 종속 | SKLauncher2 안에 유지 |
UI component는 Device API를 직접 호출하지 않는다. 사용자 action을 callback으로 올리고 앱이 Framework Client를 호출한다. Task 진행 UI는 TaskEvent를 입력받아 표현만 담당한다.
7.1 UI API 예시
<com.skix.ui.components.TaskProgressView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:statusStyle="compact" />
taskProgressView.render(TaskUiModel.from(taskEvent));
surfaceClient.open(SkixSurface.VITAL_SIGN, SurfaceArgs.empty());
파트너 앱은 SKLauncher2의 Activity나 Fragment 클래스명을 직접 알지 않는다. surfaceId, arguments schema, result contract만 사용한다.
8. 기존 경로와 호환 운영
초기 전환에서 기존 MQTT, PUI, SKLauncher2, IoT, 내부 앱 호출을 일괄 변경하지 않는다.
| 경로 | 초기 운영 | 전환 방향 |
|---|---|---|
기존 sendModuleCommand(Bundle) |
Legacy lane 유지 | 새 기능부터 typed Facade와 Gateway 적용 |
TaskManagerClient AAR |
사내 내부 adapter로 유지 | Gateway가 실행 요청을 전달하는 표준 내부 client |
| SKLauncher2 직접 호출 | 기존 기능 보존 | 공용 UI와 신규 실행부터 SDK로 전환 |
| IoT/MQTT 단일 명령 | 기존 payload 유지 | Task 성격이 필요한 요청만 task envelope 추가 |
| 승인 파트너 앱 | 기존 Binder 직접 접근 금지 | Gateway와 Partner SDK만 사용 |
같은 물리 자원을 사용하는 legacy 실행과 managed Task가 동시에 들어오면 TaskManager의 ingress와 자원 정책으로 단계적으로 편입한다. Legacy를 즉시 Task로 강제하지 않되, 이동, 청정, 카메라처럼 충돌 비용이 큰 도메인부터 managed concurrency로 전환한다.
9. 단계별 리팩터링 운영안
Phase 0. 기준선과 카탈로그
- 기준 MR6 브랜치와 TaskManager 구현을 정합한다.
- DeviceAgent public method, domain owner, caller, callback, 완료 증거를 inventory로 고정한다.
- SKLauncher2 resource를 token, component, surface, internal로 분류한다.
- 파트너 API에서 제외할 privileged 기능과 개인정보 기능을 명시한다.
완료 기준은 호출·상태·이벤트·보안 owner가 없는 기능이 inventory에 남지 않는 것이다.
Phase 1. Read-only 공개 API
- DeviceAgent 제품 Runtime Binder 안에 공개 Facade와
skix-framework-contract/client초안을 만든다. - identity, certificate allowlist, API version, capability discovery만 제공한다.
- state snapshot은 read-only capability부터 공개한다.
- 기존 DeviceAgent 실행 경로는 변경하지 않는다.
이 단계에서 보안 경계와 SDK 연결을 검증하고 물리 동작 회귀 위험을 분리한다.
Phase 2. 저위험 Action과 TaskManager 연결
- 화면 전환, TTS, 상태성 설정처럼 복구 가능한 기능부터 Action API를 연결한다.
- Gateway Task Adapter가
TaskSubmitRequest를 생성한다. - Task event ownership과 public reason mapping을 검증한다.
- raw Task 필드를 파트너가 주입할 수 없는지 부정 시험을 수행한다.
Phase 3. UI Foundation
- semantic token과 primitive component를 별도 namespaced AAR로 추출한다.
- SKLauncher2와 partner sample app이 같은 UI AAR를 사용한다.
- screenshot, locale, accessibility, resource collision 회귀 시험을 추가한다.
Phase 4. 이동·청정과 제한 Workflow
- 이동과 청정 Facade를 Gateway capability로 공개한다.
- partner-composable로 승인된 operation만 Workflow 단계에 사용할 수 있게 한다.
- 최대 단계 수, timeout, 위험도, 동의, foreground 조건을 정책화한다.
- PUI 취소와 물리 완료 증거까지 terminal event로 검증한다.
Phase 5. 파트너 Pilot과 운영
- partner certificate 등록, 회전, 폐기 절차를 검증한다.
- Test Kit과 sample app, API 문서, 호환성 matrix를 배포한다.
- API 사용량, 거절 reason, task terminal coverage, binder death를 관측한다.
- 안정화된 API만 v1으로 고정하고 raw API deprecation을 시작한다.
10. 검증 항목
| 영역 | 필수 시험 |
|---|---|
| 신원 | 승인 certificate 허용, 미승인·변조·package 불일치 거절 |
| 권한 | capability별 allow/deny, user consent, emergency 우회 거절 |
| API 호환 | SDK 구버전 협상, capability 미지원, deprecated API 처리 |
| Task 연계 | Action과 Workflow가 TaskManager에 한 번만 등록되는지 확인 |
| 생명주기 | progress, cancel, timeout, binder death, terminal event 전달 |
| 상태 | snapshot freshness, 민감 필드 제거, event 소유권 필터 |
| UI | theme/resource collision, 접근성, 다국어, Task 상태 표현 |
| Legacy | 기존 DeviceControlProxy, MQTT, SKLauncher2 동작 parity |
| 실기기 | 이동·청정·화면·TTS 등 도메인별 실행과 물리 완료 증거 |
11. 1차 의사결정
| 항목 | 결정 |
|---|---|
| Framework 배치 | AOSP frameworks/base가 아니라 SKIX namuh Product Framework Layer |
| 공개 경계 배치 | 1차는 DeviceAgent 제품 Runtime Binder 내부 논리 모듈. 파트너 보안·독립 OTA·장애 격리 근거가 있을 때 별도 privileged service 검토 |
| TaskManager 관계 | Gateway가 Task/Workflow를 생성하고 TaskManager가 실행 생명주기를 독점 |
| 파트너 범위 | 별도 인증서의 사전 승인 파트너 앱 포함 |
| 공개 실행 계약 | raw method가 아니라 capability + operation |
| UI 전략 | SKLauncher2 전체 공유가 아니라 token, component, surface 계약 분리 |
| 호환 전략 | 기존 경로 유지, 신규 기능 우선 적용, 도메인별 점진 전환 |
12. 다음 검토 항목
- 승인 파트너 범위와 장애 격리 요구가 별도 process 비용을 정당화하는지 측정한다.
- v1 파트너에게 공개할 capability 범위를 read-only, UI, TTS, 이동, 청정 중에서 선정한다.
- 파트너 인증서 allowlist를 system image, signed policy package, 관리 서버 중 어디서 갱신할지 결정한다.
- SKLauncher2에서 가장 먼저 추출할 token과 component 10개를 선정한다.
- 기존
TaskManagerClient의sendRaw와 전체 constants를 Internal API로 분류할 방식을 결정한다.