MR7 앱·SDK 모듈별 환경과 책임
기준:
a1-packages-mr6,quber/mr7,7695c9a85fedf6d756490e51c07f6e09f568a4bd. 추적 소스의 정적 분석이다. 미추적 TaskManagerClient 및 작업 산출물은 포함하지 않는다. APK·AOSP 빌드나 기기 동작 검증은 이번 분석 범위가 아니다.
범위는 git show 7695c9a8:path로 읽은 추적 파일이다. 아래 책임/입출력은 코드에 나타난 컴포넌트와 호출만 요약하며, APK 설치·서명 검증·실기기 동작은 확인하지 않았다.
이 모듈에서 개발을 시작한다면
기능별 수정 위치는 DeviceAgent뿐 아니라 Launcher2·IotAgent·UpdateAgent의 실제 파일/메서드를 연결한다. 아래 표는 환경과 책임을 설명하며, 영향 범위표는 다른 모듈과의 협업 범위를 정하는 데 사용한다.
모듈 인벤토리
| 모듈 | 종류·빌드/설치 사실 | 매니페스트·권한/엔트리 | 주요 코드와 관찰된 입출력 |
|---|---|---|---|
| DeviceAgent | 제품 제어 앱, system UID / persistent / privileged 구성 | Binder·부팅·예약 Receiver 및 WSS/Streaming Service | 실행 환경과 요소 책임, 51개 Manager 책임, 정책에서 상세 설명 |
| AMRAgent | Android.mk 패키지, system/priv-app, platform 서명, private API [apps/AMRAgent/Android.mk:19-21,61-64] |
system shared UID, 네트워크/Wi-Fi, alert window, keystore 권한; exported AMRAgent service와 boot receiver [apps/AMRAgent/AndroidManifest.xml:3-24,26-43] |
AMRAgent/AMRCmdDispatcher가 IAMRControl AIDL 명령을 받아 AMR 소켓 명령·상태 모델로 변환하고 callback/상태를 반환 [apps/AMRAgent/java/com/sk/airbot/amragent/AMRAgent.java:151-164]; 세부 command/model 다수 [git tree apps/AMRAgent] |
| AirbotSdkProject/app | Gradle 샘플 앱, compile/min/target 33/33/33 [apps/AirbotSdkProject/app/build.gradle:6-12] | manifest는 application만 있고 컴포넌트/권한 없음 [apps/AirbotSdkProject/app/src/main/AndroidManifest.xml:2-15] | ExampleUnitTest와 ExampleInstrumentedTest만 존재 [apps/AirbotSdkProject/app/src/test/java/com/sk/airbot/airbotsdkproject/ExampleUnitTest.java; apps/AirbotSdkProject/app/src/androidTest/java/com/sk/airbot/airbotsdkproject/ExampleInstrumentedTest.java]; |
| AirbotSdkProject/devicecontrolapi | Gradle library, min/target 28/33 [apps/AirbotSdkProject/devicecontrolapi/build.gradle:6-11] | 빈 library manifest [apps/AirbotSdkProject/devicecontrolapi/src/main/AndroidManifest.xml:2-4] | AIDL IDeviceControl, streaming, WSS 인터페이스와 DeviceControlProxy, Front/Remote/Proxy 구현 [apps/AirbotSdkProject/devicecontrolapi/src/main/aidl; apps/AirbotSdkProject/devicecontrolapi/src/main/java]; DeviceAgent 서비스에 대한 클라이언트 API |
| DeviceControlAPI | Make static Java library, private API [apps/DeviceControlAPI/Android.mk:4-18] | 빈 manifest [apps/DeviceControlAPI/AndroidManifest.xml:1-8] | DeviceControlProxy 및 DeviceControl AIDL의 정적 라이브러리; DeviceAgent AIDL 경로를 참조 [apps/DeviceControlAPI/Android.mk:10-14] |
| ErrorProvider | privileged package, system/priv-app; platform 인증서 주석 처리, LOCAL_PRIVILEGED_MODULE [apps/ErrorProvider/Android.mk:5-18,28] |
exported provider/cleanup service/boot receivers; read/write/access 자체 권한(ACCESS는 signature 또는 privileged), QUERY_ALL_PACKAGES [apps/ErrorProvider/AndroidManifest.xml:20-54] | Provider가 error data CRUD 경계, cleanup/OTA upgrade receiver가 lifecycle 입력 [apps/ErrorProvider/src: tracked source tree] |
| EventAssetAgent | Gradle app, compile/min/target 33/31/33; release signing 설정, minify [apps/EventAssetAgent/app/build.gradle:7-12,20-37] | system UID, INTERNET/record audio/foreground; exported landscape UI와 RecordingAidlService, internal upload service [apps/EventAssetAgent/app/src/main/AndroidManifest.xml:3-18,27-57] |
RecordingAidlService, RecordingManager, camera broker client, Room DB/repository/upload service가 녹화 요청을 받아 clip/thumbnail/ticket 저장 및 업로드 [apps/EventAssetAgent/app/src/main/java/com/sk/airbot/eventassetagent: service,recording,broker,data] |
| ICT | Gradle app, compile/min/target 33/33/33 [apps/ICT/app/build.gradle:21-27] | system UID; location/Wi-Fi/network, INTERNET, kill background, battery optimization [apps/ICT/app/src/main/AndroidManifest.xml:2-17]; exported MAIN activity [apps/ICT/app/src/main/AndroidManifest.xml:26-38] | QApplication, repositories(Charging/Driving/Sensor/VoiceTest), DeviceControlResolver, Ict가 UI 테스트 입력을 DeviceControl API로 보내고 결과를 UI로 반환 [apps/ICT/app/src/main/java/com/sk/airbot/ict/data; apps/ICT/app/src/main/java/com/sk/airbot/ict/server/Ict.kt:1-40] |
| IotAgent | Make는 이미 빌드된 IotAgent.apk를 prebuilt로 system/priv-app, platform 서명 [apps/IotAgent/Android.mk:4-22]; Gradle 소스 설정 compile/min/target 33/28/33 [apps/IotAgent/app/build.gradle:21-27] |
system UID, 광범위 네트워크/Wi-Fi/storage/alert/foreground/cache/wakelock/broadcast 권한 [apps/IotAgent/app/src/main/AndroidManifest.xml:4-33]; non-exported boot receiver와 IIotControl service [apps/IotAgent/app/src/main/AndroidManifest.xml:47-65] |
IotAgent는 MQTT/AWS client와 publish/subscribe helper를 사용 [apps/IotAgent/app/src/main/java/com/sk/airbot/iotagent/IotAgent.java:66-74,123-151]. IIotControl.Stub.sendModuleCommand의 method switch가 DeviceAgent로 전달할 입력·cloud publish를 선택 [apps/IotAgent/app/src/main/java/com/sk/airbot/iotagent/IotAgent.java:406-478]; DeviceAgent callback을 method별 처리해 cloud response/event로 publish [apps/IotAgent/app/src/main/java/com/sk/airbot/iotagent/IotAgent.java:741-838] |
| LLMAgent | Android.mk system/priv-app, platform/private API [apps/LLMAgent/Android.mk:6-8,33-35] |
manifest system UID, exported service/boot receiver, network/storage/overlay/foreground 등 [apps/LLMAgent/AndroidManifest.xml:1-40] | LLMAgent가 ILLMControl binder callback 등록 및 sendModuleCommand를 수신 [apps/LLMAgent/java/com/sk/airbot/llmagent/LLMAgent.java:54-95]; 현재 switch에는 빈 method면 반환하는 경로 [apps/LLMAgent/java/com/sk/airbot/llmagent/LLMAgent.java:90-94] |
| MCUAgent | Android.mk system/priv-app, platform/private API [apps/MCUAgent/Android.mk:6-8,33-35] |
manifest system UID, MCU service/boot receiver 권한·엔트리 [apps/MCUAgent/AndroidManifest.xml:1-40] | MCUAgent, Manager, Comm, Protocol, SerialPort가 binder/명령을 serial MCU로 보내고 수신 상태를 callback [apps/MCUAgent/java: tracked source tree] |
| PairingAgent | Make prebuilt APK, system/priv-app, platform 서명 [apps/PairingAgent/Android.mk:4-16]; Gradle source compile/min/target 33/32/33 [apps/PairingAgent/app/build.gradle:6-12] |
system UID; keystore, Bluetooth/BLE, location, Wi-Fi, audio, secure settings/time; exported pairing service/boot receiver 및 intent receiver [apps/PairingAgent/app/src/main/AndroidManifest.xml:2-33,50-78] | PairingAgentService binder method setBluetooth 등 수신, DeviceControlProxy 연결·callback [apps/PairingAgent/app/src/main/java/com/sk/airbot/pairingagent/PairingAgentService.java:37-44,87-123,228-270]. IntentReceiver는 key/auth/reset 입력을 package 지정 broadcast로 DeviceManager/GBAS에 전달 [apps/PairingAgent/app/src/main/java/com/sk/airbot/pairingagent/intent/IntentReceiver.java:11-64] |
| SKMLauncher | Gradle app, compile/min/target 33/25/33, release signing config [apps/SKMLauncher/app/build.gradle:22-30,52-66] | system UID, INTERNET/storage/custom permission/boot; exported MAIN 및 다수 activity, boot receiver [apps/SKMLauncher/app/src/main/AndroidManifest.xml:4-15,37-117] | UI launcher가 DeviceControlResolver/DeviceControlProxy를 통해 DeviceAgent 명령을 만들고, mapping/self-diagnosis에서 명시적 component로 DeviceAgent activity 실행 [apps/SKMLauncher/app/src/main/java/com/sk/airbot/skmlauncher/data/common/DeviceControlResolver.java; apps/SKMLauncher/app/src/main/java/com/sk/airbot/skmlauncher/ui/init/InitMappingCompleteFragment.java:146-149; apps/SKMLauncher/app/src/main/java/com/sk/airbot/skmlauncher/ui/manage/ManageSelfDianosisFragment.java:70-75] |
| SKMLauncher2 | Gradle app, 동일 applicationId com.sk.airbot.skmlauncher, compile/min/target 33/28/33 [apps/SKMLauncher2/app/build.gradle:25-31]; release signing config [apps/SKMLauncher2/app/build.gradle:53-67] |
system UID, SET_TIME/camera/network/storage/custom/location/Wi-Fi/boot; exported MAIN 및 다수 activity와 boot receiver [apps/SKMLauncher2/app/src/main/AndroidManifest.xml:4-23,49-141] | DeviceControlResolver, WSS camera broker client, UI managers/repositories가 DeviceAgent/WSS/camera broker 호출 [apps/SKMLauncher2/app/src/main/java/com/sk/airbot/skmlauncher/data/common,ui,wss]. OTP는 serial+secret 기반 검증, 5회 실패 lock [apps/SKMLauncher2/app/src/main/java/com/sk/airbot/skmlauncher/ui/manage/OtpAuthViewModel.java:31-40,84-103,195-210,218-275] |
| StartupService | Gradle compile/min/target 34/32/34, release signing 설정; Make는 system/priv-app, platform/private API [apps/StartupService/app/build.gradle:22-28,48-53; apps/StartupService/Android.mk:11-13,33-35] |
system UID, privileged phone/storage/foreground/boot; exported StartupService와 boot receiver [apps/StartupService/app/src/main/AndroidManifest.xml:4-15,27-45] |
StartupService.onCreate가 foreground 시작 후 OTA/factory-reset preferences를 읽고 popup을 만들며 delayed handler를 등록 [apps/StartupService/app/src/main/java/com/sk/airbot/startupservice/StartupService.java:35-53,58-99] |
| UpdateAgent | Android.mk package, system/priv-app, platform/private API, SystemUpdaterSample override [apps/UpdateAgent/Android.mk:10-14,40-44] |
system UID, protected ACTION_OTA_UPDATE, cache/network/storage/overlay/wakelock/broadcast; exported UI/service/prepare service/boot receiver [apps/UpdateAgent/AndroidManifest.xml:3-31,34-78] |
UpdateAgent binder는 callback 등록과 method OTAUpdate를 받음 [apps/UpdateAgent/java/com/sk/airbot/updateagent/UpdateAgent.java:62-109], DeviceControl callback을 받고 broadcast함 [apps/UpdateAgent/java/com/sk/airbot/updateagent/UpdateAgent.java:120-161]. UpdateManager는 UpdateEngine 비동기 상태/진행/완료 callback을 관리 [apps/UpdateAgent/java/com/sk/airbot/updateagent/UpdateManager.java:43-78] |
| VisionAgent | Android.mk system/priv-app, platform/private API [apps/VisionAgent/Android.mk:12-14,43-45] |
system UID, face recognition permission, overlay; protected vision ENABLE/REPORT/REQ/STATUS broadcasts; exported Vision/Face services·boot receiver [apps/VisionAgent/AndroidManifest.xml:3-20,22-60] | VisionAgent는 vision_data와 enable/request/report/status broadcast 계약을 정의하고 DeviceAgent AIDL report/status 메서드를 사용 [apps/VisionAgent/java/com/sk/airbot/visionagent/VisionAgent.java:68-100] |
| devicecontrolapi_test | Make 테스트 APK, private API, 인증서 주석 처리 [apps/devicecontrolapi_test/Android.mk:7-18] | exported MAIN MainActivity, 별도 권한 없음 [apps/devicecontrolapi_test/AndroidManifest.xml:1-19] |
DeviceControlAPIJavaLib를 링크하는 수동 테스트 UI [apps/devicecontrolapi_test/Android.mk:13-18; apps/devicecontrolapi_test/java/net/quber/devicecontrolapi_test/MainActivity.java] |
| permissions | 앱이 아닌 권한 설정 디렉터리 | privapp-permissions-quber_packages.xml과 생성 보조 Python 파일 |
packages.mk:33–35에서 XML을 system permissions에 복사. 권한별 대상은 플랫폼 문서 참고 |
정책 검사(코드에 실제로 보이는 것)
| 대상 | 근거와 동작 |
|---|---|
| IotAgent | manifest가 service/boot receiver를 exported=false로 선언하고 system UID·privileged 수준 권한을 요구한다 [apps/IotAgent/app/src/main/AndroidManifest.xml:4-33,47-65]. Binder sendModuleCommand는 null/빈 method를 반환하고 method switch로 허용된 동작을 분기한다 [apps/IotAgent/app/src/main/java/com/sk/airbot/iotagent/IotAgent.java:441-478]. 외부 intent는 UpdateAgent UI 등 대상 package/component를 명시하는 호출이 있다 [apps/IotAgent/app/src/main/java/com/sk/airbot/iotagent/IotAgent.java:1791; apps/IotAgent/app/src/main/java/com/sk/airbot/iotagent/mqtt/topic/CmdTopic.java:3653-3690]. |
| UpdateAgent | manifest에 protected OTA broadcast가 있고 service는 exported지만 별도 component permission은 없다 [apps/UpdateAgent/AndroidManifest.xml:10-11,62-75]. Binder는 callback null과 빈 method를 검사하고 OTAUpdate만 처리한다 [apps/UpdateAgent/java/com/sk/airbot/updateagent/UpdateAgent.java:71-109]. SelectActivity는 storage runtime permission을 checkSelfPermission으로 확인한다 [apps/UpdateAgent/java/com/sk/airbot/updateagent/ui/SelectActivity.java:54-107,145-155]. |
| PairingAgent | service exported=true이며 component permission 선언은 없다 [apps/PairingAgent/app/src/main/AndroidManifest.xml:50-57]. Binder는 callback null/빈 method를 검사하고 setBluetooth를 분기한다 [apps/PairingAgent/app/src/main/java/com/sk/airbot/pairingagent/PairingAgentService.java:87-123]. BLECentral은 Bluetooth Connect의 self permission을 확인한다 [apps/PairingAgent/app/src/main/java/com/sk/airbot/pairingagent/ble/BLECentral.java:303-310]. IntentReceiver는 수신 후 대상 package를 setPackage로 제한한다 [apps/PairingAgent/app/src/main/java/com/sk/airbot/pairingagent/intent/IntentReceiver.java:45-64]. |
| SKMLauncher / 2 | manifest가 자체 CUSTOM_PERMISSION을 선언·uses하지만 activity들에는 component-level permission이 없다 [apps/SKMLauncher/app/src/main/AndroidManifest.xml:4-15,37-117; apps/SKMLauncher2/app/src/main/AndroidManifest.xml:4-23,49-141]. Launcher2에는 UI OTP 정책(입력 형식/장비 serial·secret 결합, 5회 실패 lock)이 실제 코드로 존재한다 [apps/SKMLauncher2/app/src/main/java/com/sk/airbot/skmlauncher/ui/manage/OtpAuthViewModel.java:31-40,218-275]. SHA-1 helper와 PIN hash 사용도 있다 [apps/SKMLauncher2/app/src/main/java/com/sk/airbot/skmlauncher/data/common/SecurityUtil.java:11-22; apps/SKMLauncher2/app/src/main/java/com/sk/airbot/skmlauncher/ui/manage/ManageViewModel.java:179-180,253-254]. |
빌드·설치 환경과 확인 범위
- Make 패키지 다수는
$(PRODUCT_OUT)/system/priv-app와LOCAL_CERTIFICATE := platform을 명시한다. IotAgent/PairingAgent는 소스 Make 빌드가 아니라 이미 존재하는 APK prebuilt를 가리킨다 [apps/IotAgent/Android.mk:4-22; apps/PairingAgent/Android.mk:4-16]. Gradle signing config가 존재하지만 서명 결과를 검증하지 않았다 [apps/EventAssetAgent/app/build.gradle:20-37; apps/PairingAgent/app/build.gradle:34-51; apps/StartupService/app/build.gradle:7-20]. SKMLauncher와SKMLauncher2는 같은 namespace/applicationId를 선언한다 [apps/SKMLauncher/app/build.gradle:22-30; apps/SKMLauncher2/app/build.gradle:25-31]. 실제 제품에서 어느 artifact가 선택되는지는 제품 make 포함 관계와 설치 상태를 이 자료만으로 판정할 수 없다.appstree는 19개 항목(Android.mk 파일 1개와 디렉터리 18개이며 DeviceAgent 포함)이고, DeviceAgent를 제외한 디렉터리와 permissions를 다뤘다.DeviceControlAPI,AirbotSdkProject/devicecontrolapi는 런타임 앱이 아닌 library이고devicecontrolapi_test는 테스트 APK로 구분했다. APK 바이너리의 실제 manifest, certificate, installed path와 Binder/robot/device 실행 결과는 이 정적 추출의 증거가 아니다.
런타임 모듈별 상태·환경 계약
- AMRAgent — 소유 상태는
AMRState와 command/model 결과이며,IAMRControlBinder 입력을AMRCmdDispatcher가 AMR socket packet으로 변환한다. 수신 packet은 callback/status로 반환된다 [apps/AMRAgent/java/com/sk/airbot/amragent/AMRAgent.java:151-164; apps/AMRAgent/java/com/sk/airbot/amragent/AMRCmdDispatcher.java:1-80]. 전제는 system/priv-app platform 빌드와 AMR transport·keystore 라이브러리다 [apps/AMRAgent/Android.mk:19-36]. - MCUAgent —
Manager/Comm/Protocol이 serial port 상태와 명령을 소유하고, Binder 명령을 protocol frame으로 변환해 MCU에 쓰고 serial 수신을 상위 callback으로 보낸다 [apps/MCUAgent/java/com/sk/airbot/mcuagent/MCUAgent.java:1-80; apps/MCUAgent/java/com/sk/airbot/mcuagent/Manager.java:1-80]. 전제는 platform private API와 장치 serial node이며, 실제 node 접근은 본 추출에서 실행 검증하지 않았다 [apps/MCUAgent/Android.mk:6-35]. - IotAgent —
mAWSConnectedStatus, OTA 상태/실행 플래그, MQTT client와 DeviceControl 연결을 소유한다 [apps/IotAgent/app/src/main/java/com/sk/airbot/iotagent/IotAgent.java:225-281]. MQTT 상태 오류/비연결 및 Wi-Fi disconnect에서는setAWSIotServiceConnected(false)를 기록하고, 연결 완료에서 true를 기록한다 [apps/IotAgent/app/src/main/java/com/sk/airbot/iotagent/IotAgent.java:1031-1046,1111-1116,1502-1510,1568-1572,1695-1696]. OTA는 jobid가 비어 있지 않을 때 배터리 30% 미만 또는 station 부재, vital sign/Live View/Safe Care 실행 중이면 FAILED 사유를 만들고, 조건 통과 시 UpdateAgent UI를 explicit component로 연다. 빈 jobid의 Samba/local 요청은 이 상태 검사를 건너뛴다 [apps/IotAgent/app/src/main/java/com/sk/airbot/iotagent/IotAgent.java:1756-1792]. 환경 전제는 MQTT·네트워크·DeviceAgent Binder와 system privileges다 [apps/IotAgent/app/src/main/AndroidManifest.xml:4-33]. - UpdateAgent —
UpdaterState와UpdateManager가 UpdateEngine 상태·오류·진행을 소유한다 [apps/UpdateAgent/java/com/sk/airbot/updateagent/UpdateManager.java:43-78].OTAUpdateBinder 입력, payload 준비/UpdateEngine 비동기 callback, DeviceControl callback을 거쳐 OTA broadcast/job status로 내보낸다 [apps/UpdateAgent/java/com/sk/airbot/updateagent/UpdateAgent.java:71-161]. 환경 전제는 protected OTA broadcast, cache/filesystem·external storage와 privileged UpdateEngine 접근이다 [apps/UpdateAgent/AndroidManifest.xml:10-31; apps/UpdateAgent/README.md:172-193]. - PairingAgent — BLE/classic Bluetooth/Wi-Fi pairing 상태와 pairing DB를 소유한다.
setBluetooth등 DeviceControl 명령은 BLE/classic manager로 변환되고, GATT/Wi-Fi 결과는getWifiStatus/callback 및 package 제한 broadcast로 반환된다 [apps/PairingAgent/app/src/main/java/com/sk/airbot/pairingagent/PairingAgentService.java:110-123,228-270; apps/PairingAgent/app/src/main/java/com/sk/airbot/pairingagent/ble/BLECentral.java:967-1007,1334-1373; apps/PairingAgent/app/src/main/java/com/sk/airbot/pairingagent/intent/IntentReceiver.java:45-64]. 환경 전제는 Bluetooth/location/Wi-Fi 권한과 장치 GATT/profile이다 [apps/PairingAgent/app/src/main/AndroidManifest.xml:8-33]. - SKMLauncher2 — UI navigation, self-test/manage 상태, OTP lock 상태를 소유한다. UI action은 DeviceControlResolver/WSS camera broker 호출로 변환되고, 결과는 ViewModel/UI state로 반영된다 [apps/SKMLauncher2/app/src/main/java/com/sk/airbot/skmlauncher/data/common/DeviceControlResolver.java:1-16; apps/SKMLauncher2/app/src/main/java/com/sk/airbot/skmlauncher/ui/manage/OtpAuthViewModel.java:84-103]. OTP는 serial과 secret이 있어야 생성·검증되며 5회 실패 시 lock 상태를 저장한다 [apps/SKMLauncher2/app/src/main/java/com/sk/airbot/skmlauncher/ui/manage/OtpAuthViewModel.java:218-275]. 전제는 camera/location/Wi-Fi와 DeviceAgent/WSS/camera broker 패키지다 [apps/SKMLauncher2/app/src/main/AndroidManifest.xml:20-32].
- EventAssetAgent — recording manager/Room repository가 clip·thumbnail·upload ticket 상태를 소유한다. exported AIDL recording service 입력을 camera broker/recording pipeline으로 변환해 DB에 저장하고 internal upload service가 업로드한다 [apps/EventAssetAgent/app/src/main/AndroidManifest.xml:46-57; apps/EventAssetAgent/app/src/main/java/com/sk/airbot/eventassetagent/recording/RecordingManager.java:1-100; apps/EventAssetAgent/app/src/main/java/com/sk/airbot/eventassetagent/data/db/EventAssetDatabase.java:1-68]. 전제는 RECORD_AUDIO/foreground, camera broker package visibility와 broker AAR이다 [apps/EventAssetAgent/app/src/main/AndroidManifest.xml:6-18; apps/EventAssetAgent/app/build.gradle:73-79].
- VisionAgent — vision enable/running/stop 및 camera status를 소유한다. vision_data의 status/camera broadcast를 받고, enable/request를 vision_data로 보내 report/status broadcast와 DeviceAgent AIDL callback으로 내보낸다 [apps/VisionAgent/java/com/sk/airbot/visionagent/VisionAgent.java:68-100; apps/VisionAgent/AndroidManifest.xml:22-60]. 전제는 protected broadcast와 face recognition/overlay 권한, vision_data 패키지다 [apps/VisionAgent/AndroidManifest.xml:10-20].
Make의 prebuilt 선언과 같은 디렉터리의 Gradle 소스는 구분해야 한다. 이 소스가 제품 이미지 속 prebuilt APK와 일치하는지는 해시·빌드 이력 대조가 필요하다.
Connections
상세 분석 목차 · 기존 전체 SVG 지도 · 정책 조건표 · 실행 시나리오