SKIX StateManager 읽기 전용 투영 구현
한 문장으로: 여러 Agent와 센서에서 올라온 값을
source + 관측 시각 + freshness가 포함된 typed 최신 상태로 제공하는 서비스다. 정책을 판단하거나 하드웨어 명령을 실행하지 않는다.
| 입력 | StateManager가 하는 일 | 출력 |
|---|---|---|
| Agent/센서 관측값 | 타입·시간 순서·lifecycle을 검증하고 key별 최신 record를 보존 | current, fresh, immutable snapshot |
전체 리팩터링 맥락은 SKIX 리팩터링 한눈에 보기에서 먼저 확인할 수 있다.
결론
b77c2c5에서 SKIX framework의 첫 번째 구체적인 Control ManagerService로
StateManagerService를 추가했다. 이 서비스는 Agent/DeviceAgent/센서가 가진
물리 상태의 소유자가 아니라, 관측값을 typed key별 최신 record로 투영하는
수명주기 관리 컴포넌트다. 따라서 주행 중 틸팅 다운 같은 정책과 actuator 명령을
이 서비스에 넣지 않는다. 상태 투영, Policy 판단, Task 실행, Evidence 증명은
서로 다른 소유권으로 유지한다.
Connectivity, StationLocation, LLM pipeline, AirQuality, Battery의 다섯 관측을 typed state로 변환하는 adapter까지 추가했다. 이 문서는 MR7 소스의 JVM 수준 구현과 MR6 producer 연결 상태를 구분한다. Android 부팅, Binder, AOSP product packaging, DeviceAgent 연결 및 실기기 동작은 이 문서의 검증 범위가 아니다.
현재 구현
기준 브랜치: refactor/blueprint-foundation
기준 커밋: 5fbf2e4 refactor(power): split battery manager service domain
| 상태 | 근거 | 의미 |
|---|---|---|
| Implemented | skix-framework/base/services/core/java/com/sk/airbot/frameworkservice/server/control/state/StateManagerService.java |
FrameworkManagedService의 STATE identity, shared supervisor affinity, lifecycle gate, publish/read API |
| Implemented | .../state/StateKey.java, StateRecord.java |
typed key(id + Class<T>), immutable observation metadata, freshness 판정 |
| Implemented | .../state/StateProjectionException.java |
not_ready, type_mismatch, out_of_order 안정 코드 |
| Implemented | .../runtime/SkixFrameworkCompositionRoot.java |
createBaseRuntime()가 같은 supervisor로 Runtime + State를 명시 구성 |
| Implemented | .../control/lifecycle/FrameworkServiceRegistry.java, ServiceLifecycleSupervisor.java |
등록/의존성 순서, READY 전이, boot phase, rollback/stop 및 health snapshot |
| Implemented | .../domain/iot/IotStateKeys.java, AwsIotConnectivityObservationAdapter.java |
MR6 AWS IoT boolean callback을 typed CONNECTED/DISCONNECTED record와 5분 freshness로 변환 |
| Source reference | MR6 apps/DeviceAgent/app/src/main/java/com/sk/airbot/deviceagent/task/observation/ConnectivityObservationBridge.java |
source·TTL·반복 callback freshness 계약의 근거. legacy semantic event dedup은 기존 경로에 유지 |
| Implemented | .../domain/amr/StationLocationEvidence.java, AmrStateKeys.java, StationLocationObservationAdapter.java |
MR6 station evidence subset을 typed value와 10분 freshness로 변환하고 fingerprint dedup 보존 |
| Source reference | MR6 .../StationLocationObservationBridge.java |
source·TTL·facts·fingerprint·cloud 판단 필요 assessment의 근거 |
| Implemented | .../domain/llm/LlmPipelineState.java, LlmStateKeys.java, LlmPipelineObservationAdapter.java |
MR6 status 0..12를 content-free typed 다축 상태와 60초 freshness로 변환. unknown status는 억제하고 반복 callback은 freshness 갱신 |
| Implemented | .../domain/airquality/IndoorAirQualityObservation.java, AirQualityStateKeys.java, AirQualityObservationAdapter.java |
MR6 측정 문자열과 warming_up/ready/invalid를 보존하는 60초 periodic projection |
| Implemented | .../domain/power/PowerManagerService.java, BatteryObservation.java, PowerStateKeys.java, BatteryObservationAdapter.java |
Battery를 System과 분리한 독립 lifecycle owner와 5분 periodic projection |
| Implemented | .../manager/SkixPowerManager.java, SkixDeviceManagers.power() |
typed client Manager. 기존 SkixManageManager battery API는 Power manager로 위임해 호환 유지 |
| Source reference | MR6 LlmPipelineObservationBridge.java, AirQualityManager.set(...), BatteryManager.setBatteryInfo(...), SourceFreshnessTracker.java |
status 매핑, 유효성, source 및 TTL 계약 근거 |
| Designed | ISkixFrameworkService, Android host, typed provider adapter |
Binder/SystemServer/process 경계 및 실제 producer 연결은 후속 단계 |
| Device-unverified | Android image, AOSP prebuilt, DeviceAgent/ROS 2/MCU/HAL/actuator | boot, IPC 권한, crash/restart, 지연 및 물리 동작 미검증 |
기존 DeviceAgent의 server/control/state/StateRecord.java는 호환성을 위해
그대로 남는다. 이번 커밋은 이를 자동 forwarding하거나 기존 writer의 소유권을
빼앗지 않는다.
- 이 그림이 답하는 질문: IoT·AMR·LLM·공기질·배터리 상태를 정책과 앱이 한곳에서 어떻게 읽는가?
- 읽는 순서: 왼쪽 관측값 수집에서 가운데 읽기 전용 상태를 거쳐 오른쪽 정책·Task·앱 사용처로 읽는다.
- 현재 검증 범위: 5개 typed adapter와 최신성 규칙은 JVM 검증됐다. 실제 producer와 ROS 2·HAL 연결은 아직 완료되지 않았다.
API 계약과 lifecycle
composition root
-> one ServiceLifecycleSupervisor
-> FrameworkServiceRegistry.register(STATE)
-> StateManagerService.onStart()
-> STATE = READY
-> publish(StateKey<T>, StateRecord<T>)
-> current / fresh / snapshot
서비스 API의 의도는 다음과 같다.
| API | 계약 |
|---|---|
publish(key, record) |
READY에서만 허용. key의 declared type과 value가 다르면 type_mismatch; 기존 record보다 observedAtMillis가 크지 않으면 out_of_order |
current(key) |
가장 최근 record를 반환. stale이어도 관측 이력의 최신값이므로 반환 |
fresh(key, nowMillis) |
nowMillis 기준 freshness window 안의 record만 반환. stale/missing은 empty |
snapshot() |
현재 map의 detached immutable view. 호출자가 map을 변경할 수 없음 |
StateRecord는 value, source, observed time, freshness duration을 보존한다.
source와 value는 null/blank를 허용하지 않으며 시간 값은 음수가 될 수 없다.
record 내부 value의 deep copy는 하지 않으므로 producer는 immutable value를
사용해야 한다. 시간 차이 계산은 overflow에 안전한 범위 조건을 사용한다.
모든 read/write는 서비스 lifecycle이 READY가 아니면 not_ready로 거부된다.
이는 상태값의 유효성과 서비스 lifecycle을 혼동하지 않기 위한 것이다. 예를
들어 READY인 서비스가 가진 connectivity record가 stale일 수 있고, 반대로
fresh record가 있어도 서비스가 STOPPED면 API를 호출할 수 없다.
AOSP 비교 근거
로컬 AOSP 기준은 다음 소스다.
/home/silogood/work/1.A1_SoC_new/SoC/g13qcs6490/LA.QSSI.13.0.R1/frameworks/base/services/core/java/com/android/server/SystemService.java는
서비스의 onStart()와 반복적인 onBootPhase() lifecycle을 정의한다.
SystemServiceManager.java는 서비스를 등록한 뒤 onStart()를 호출하고, 증가하는
boot phase를 등록된 서비스 순서로 전달한다.
SKIX registry는 이 운영 개념을 참고하지만 Android SystemService 자체가
아니다. 현재는 transport-free plain Java이며, SystemServer, Binder, SELinux,
init 또는 Android process가 호출하지 않는다. 향후 hybrid hosting에서 얇은
Android bridge가 composition root를 만들고, 실제 state/provider adapter를
주입하는 방향이다.
소유권 분리
| 영역 | 소유자 | StateManager와의 관계 | 금지사항 |
|---|---|---|---|
| 관측 최신값/시간/source/freshness | StateManagerService |
typed projection 입력을 받음 | actuator 명령, 정책 추론 |
| 도메인 의미 상태 | 각 domain ManagerService (AMR, Vision, IoT, LLM, Cleaning, Air Quality, Power, UI 등) |
domain adapter가 key를 정하고 publish | 다른 도메인의 writer를 몰래 호출 |
| 정책/충돌 해결 | Policy manager 및 도메인 정책 | state/evidence를 읽어 Can/Cannot 판단 | 상태 저장소가 우선순위를 임의로 결정 |
| Task/workflow 실행 | TaskManager / Workflow | 상태와 정책을 읽고 명령 lifecycle을 관리 | 상태 observation을 새 Task로 오인 |
| 명령/물리 효과 | typed provider, Agent, ROS 2, MCU/HAL | 실행 결과를 observation/evidence로 되돌림 | framework projection이 hardware 직접 호출 |
| 결과 증명/설명 | Evidence manager | task correlation과 observation을 결합 | 단순 latest state를 terminal success로 간주 |
이 분리는 “주행 중이면 틸팅 down” 같은 사양을 다음처럼 다룰 수 있게 한다. Mobility/AMR domain이 주행 상태를 관측해 state key로 publish하고, Policy가 틸팅 요구와 금지 조건을 평가하며, TaskManager가 허용된 command를 실행한다. 실행 결과와 실제 자세는 provider observation/evidence로 다시 투영한다. 여러 정책이 충돌하면 StateManager가 마지막 writer를 정하는 것이 아니라 Policy/ Resource 계층의 명시적인 priority와 rejection reason이 결정한다.
관측 adapter의 이식 경계
Connectivity, StationLocation, LLM pipeline, AirQuality, Battery의 typed 변환 adapter는 MR7에 구현됐다. 다만 실제 DeviceAgent producer가 이를 호출하는 process/Binder wiring과 dual-publish parity는 아직 구현 증거가 아니다.
MR6 Connectivity bridge semantics
-> AwsIotConnectivityObservationAdapter (구현·JVM 검증)
-> IotStateKeys.AWS_IOT_CONNECTION
-> StateManagerService.publish()
-> domain manager / policy / task / evidence read
MR6 StationLocation bridge semantics
-> StationLocationObservationAdapter (구현·JVM 검증)
-> AmrStateKeys.STATION_LOCATION_EVIDENCE
-> StateManagerService.publish()
MR6 LlmPipelineObservationBridge
-> LlmPipelineObservationAdapter
-> LlmStateKeys.PIPELINE
-> StateManagerService.publish()
MR6 AirQualityManager.set / BatteryManager.setBatteryInfo
-> AirQualityObservationAdapter / BatteryObservationAdapter
-> AirQualityStateKeys / PowerStateKeys
-> StateManagerService.publish()
이식 시 확인할 항목은 source별 TTL 단위, timestamp clock, duplicate/fingerprint 규칙, Bundle/문자열 payload의 typed value 변환, producer sequence, process death 재연결, 그리고 기존 DeviceAgent writer와의 dual-publish 기간이다. adapter가 완성되기 전에는 MR6 semantics를 “호환된다”고 표시하지 않는다.
StationLocation의 MR6 fingerprint는 station_area_id를 포함하지 않는다. 현재
adapter는 이 동작을 호환성 테스트로 보존했다. station ID만 변경된 관측도 새로
발행해야 하는지는 dual-publish shadow 결과를 보고 별도 정책 변경으로 결정한다.
후속 gate
- DeviceAgent의 Connectivity, StationLocation, LLM, AirQuality, Battery 생산 지점에 legacy + typed dual-publish bridge를 연결한다.
- 다섯 source의 shadow 비교로 누락·순서·TTL 차이를 측정한다.
- Evidence manager가 state snapshot을 terminal success의 단독 근거로 쓰지 않도록 correlation과 provider acknowledgement 계약을 정의한다.
- Policy/Resource에 precedence, conflict, rejection code를 추가하고 틸팅을 포함한 domain policy 사례를 테스트한다.
- 그 다음 TaskManager/Workflow가 State + Policy + Resource를 읽고 provider
command를 실행하도록 MR6
task_manager브랜치 동작을 compatibility port한다. - 마지막으로 Binder/SystemServer/init/SELinux/package를 연결하고 boot, crash/restart, permission, OTA 및 실기기 actuator 검증을 수행한다.
검증 범위
StateManagerServiceTest, SkixFrameworkCompositionRootTest,
AwsIotConnectivityObservationAdapterTest, StationLocationObservationAdapterTest,
LlmPipelineObservationAdapterTest, AirQualityObservationAdapterTest,
BatteryObservationAdapterTest는 typed key,
record validation, lifecycle affinity/gating, ordering, freshness, snapshot,
composition과 다섯 adapter 변환을 FrameworkService JVM 89개 테스트에서 검증한다.
SDK JVM 34개 테스트는 새 SkixDeviceManagers.power() API와 기존 Manage 호환 API의 공존을
포함한다. 저장소 tree verifier와 git diff --check는
정적 구조를 검증한다. 이 결과는 Android boot 또는 물리 로봇 acceptance 증거가
아니다.