A2A 테스트 진입점과 운영 음성 경로 정합성 감사
기준 시점은 2026-07-27이다. 이 문서는 이미 완료한 Cloud Planner,
DeviceAgent TaskManager, IotAgent, Task Monitor 메서드 계약 감사를 반복하지 않는다.
A2A Text Console -> local Lambda shim -> TextInputTestReceiver ->
ForegroundService -> Cloud A2A Planner가 실제 음성 세션을 어느 지점부터 동일하게
재현하는지와, 테스트 경로가 운영 상태를 오염시키는 지점을 별도로 검증한다.
1. 결론
현재 /device-text는 STT가 끝난 이후의 음성 처리 경로를 상당 부분 재사용한다.
PAUSE, 기기 상태 snapshot, processVoiceCmd, Cloud request, TTS, RESUME,
TTS 종료 후 TaskManager dispatch까지는 실제 코드를 탄다.
그러나 아래 항목 때문에 전체 음성 E2E와 동일하다고 판정할 수 없다.
- wake word, 녹음, STT decode/substitution, STT 유효성 검사, 실사용 사전 권한 검사를 건너뛴다.
- local shim은 현재 인증 없이
0.0.0.0:18080에 열려 있고 CORS도*이다. - public console의
ADB serial과Device ID입력값이 on-device 전송 대상을 실제로 변경하지 않는다. - 테스트 Cloud URL override와 persistent property가 다음 실제 음성 turn까지 남을 수 있다.
/device-text의 HTTP 200은 ADB broadcast 접수 성공일 뿐 Planner 또는 TaskManager 완료가 아니다.- shim trace가 device/session별로 격리되지 않아 여러 실행의 관찰 결과가 섞인다.
따라서 현재 판정은 다음과 같다.
| 검증 범위 | 판정 |
|---|---|
Cloud Planner 단독 /text |
사용 가능 |
| post-STT on-device -> Cloud -> TaskManager | 조건부 사용 가능 |
| 실제 음성 전체 E2E | 미증명 |
| 여러 기기/여러 세션 동시 검증 | 부적합 |
| 상시 활성화된 운영 진단 인터페이스 | 부적합 |
2. 현재 런타임 증거
감사 시점의 실기기와 shim 상태는 다음과 같다.
ADB serial: 192.168.123.116:5555
Device ID: MWPA1M10KRDWA0425Z00411
MaumAi: /system/priv-app/MaumAi, versionName=1.19.28, shared uid=1000
adb reverse: tcp:18080 -> tcp:18080
shim bind: 0.0.0.0:18080
shim ADB target: 192.168.123.116:5555
text input property: persist.sys.debug.maumai.text_input_enabled=1
Cloud override property: persist.sys.debug.maumai.cloud_api_base_url=http://127.0.0.1:18080
임의 Origin으로 보낸 preflight와 trace 요청에도 아래 헤더가 반환됐다.
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type
이는 추정이 아니라 현재 실행 중인 shim에서 재현한 결과다.
3. 실제 데이터 흐름
flowchart LR
UI["Public A2A Text Console"]
SHIM["Local Lambda shim"]
ADB["adb shell am broadcast"]
RX["exported TextInputTestReceiver"]
FG["ForegroundService"]
CLOUD["local lambda_handler / A2A Planner"]
TM["DeviceAgent TaskManager"]
EVT["task_event callback"]
UI -->|POST /device-text| SHIM
SHIM -->|server --adb-serial| ADB
ADB --> RX
RX -->|USE_TEXT_INPUT| FG
FG -->|PAUSE + device snapshot| FG
FG -->|/llm/invoke| CLOUD
CLOUD -->|device task/workflow contract| FG
FG -->|TTS 종료 후 dispatch| TM
TM --> EVT
EVT -->|/task-event| SHIM
SHIM --> CLOUD
Cloud shim에 직접 보내기는 첫 네 구간을 우회한다.
Console -> POST /text -> local lambda_handler
따라서 이 버튼의 결과로 ForegroundService, DeviceAgent PAUSE/RESUME, TaskManager 등록 또는 callback을 검증했다고 말하면 안 된다.
4. 요구사항 추적 매트릭스
| ID | 요구사항 | 구현 증거 | 테스트 증거 | 상태 |
|---|---|---|---|---|
| ING-01 | 텍스트가 실제 post-STT 경로를 사용 | VOICE_PIPELINE_TEST=true, processTextAsVoiceInput -> processVoiceCmd |
기존 on-device 단위 테스트와 실기기 E2E 로그 | 조건부 충족 |
| ING-02 | 음성 세션 중 TaskManager PAUSE/RESUME 보존 | text entry의 sendStatePause, TTS finalize의 sendStateResume |
기존 실기기 workflow 수행 | 충족 |
| ING-03 | 테스트가 실제 음성 endpoint를 오염시키지 않음 | 전역 cloudApiBaseUrlOverride, persistent property fallback |
cleanup 자동화 없음 | 미충족 |
| ING-04 | UI에 지정한 기기가 실제 ADB 대상 | UI는 adb_serial 전송, shim은 server.adb_serial만 사용 |
source 대조 | 미충족 |
| ING-05 | HTTP 성공이 Planner/TaskManager 성공과 구분됨 | /device-text는 adb return code로 즉시 200 |
correlation wait 없음 | 미충족 |
| ING-06 | trace가 device/session별 격리 | 단일 trace_events[-200:], /trace?since=만 지원 |
현재 60개 /text 실행이 한 stream에 누적 |
미충족 |
| ING-07 | 운영 사전 검증과 동일 | direct_gemini는 terms/usage 검증을 우회 |
local validation unit test | 의도적 미충족 |
| ING-08 | 텍스트 입력 ASR confidence가 결정적 | 기존 LlmStore.lastAsrConfidence 재사용 |
명시적 text confidence 없음 | 미충족 |
| ING-09 | 진단 ingress가 권한으로 보호됨 | exported receiver, 별도 permission 없음 | runtime manifest와 property=1 | 미충족 |
| ING-10 | 실패 시 PAUSE가 반드시 해제됨 | processVoiceCmd에 같은 timeout catch가 중복되고 일반 예외 finally reset 없음 |
해당 실패 회귀 테스트 없음 | 미충족 |
5. 불일치 항목
P0. local shim이 ADB와 Planner를 인증 없이 LAN 및 모든 웹 Origin에 노출
local_lambda_shim은 기본 host가 0.0.0.0이고 다음 endpoint를 인증 없이 연다.
/device-text: shim 프로세스 권한으로 ADB broadcast 실행/text,/chat,/llm/invoke: local Gemini/Planner 호출/task-event: workflow callback 주입/trace: 사용자 발화, Planner 응답, workflow 상태 조회
현재 CORS가 *이므로 임의 웹 페이지가 개발 PC의 localhost shim에 요청할 수 있다.
실기기 E2E에서는 127.0.0.1 bind를 기본으로 하고, 허용 Origin 또는
one-time developer token을 별도 계약으로 두기 전에는 LAN bind를 금지해야 한다.
P0. release MaumAi의 exported test receiver와 persistent enable property
release manifest에도 TextInputTestReceiver가 exported 상태이며 signature permission이
없다. property가 꺼져 있으면 receiver 내부에서 거부하지만, 현재 기기는
persist.sys.debug.maumai.text_input_enabled=1이다. 이 상태에서는 broadcast를 보낼 수
있는 다른 앱이 테스트 입력과 Cloud URL을 주입할 수 있다.
완료 기준은 아래 중 하나다.
- 진단 전용 product flavor에서만 component를 포함한다.
- platform signature permission으로 보호하고 호출자까지 검증한다.
- 최소한 테스트 종료 시 component/property를 자동 비활성화한다.
P1. UI의 기기 선택값과 실제 대상이 다름
console은 adb_serial과 deviceId를 payload에 넣지만 /device-text handler는
payload의 adb_serial을 읽지 않고 shim 시작 시의 --adb-serial만 사용한다.
on-device Cloud request의 device ID도 기기 내부 serial을 사용한다.
따라서 현재 UI 값의 의미는 다음과 같이 제한해야 한다.
| 필드 | 실제 효력 |
|---|---|
ADB serial |
실행 중 shim 설정과 일치하는지 확인하는 참고값 |
Device ID |
/text 직접 Cloud 호출에만 사용 |
Session ID |
receiver를 거쳐 voice_context.session_id로 전달 |
UI가 임의 serial을 runtime에 적용하도록 고치는 것보다 /health가 shim의
authoritative serial/device ID를 반환하고 console은 이를 read-only로 표시하는 편이
안전하다.
P1. Cloud URL override가 다음 음성 turn에 남음
receiver extra의 URL은 NetworkModule.cloudApiBaseUrlOverride 전역값으로 저장된다.
turn 종료 시 이 값을 해제하는 코드가 없다. persistent property도 fallback으로
사용되므로 현재 설정은 재부팅 이후에도 남는다.
이 때문에 텍스트 E2E 직후 실제 음성을 사용해도 배포 Cloud 대신 local shim을 계속
호출할 수 있다. 해결은 request-scoped endpoint 또는 test turn finally에서의
one-shot clear이며, 운영/테스트 endpoint 선택을 전역 mutable state로 두지 않는 것이
최종 방향이다.
P1. ADB 접수와 E2E 성공이 같은 성공으로 표시됨
/device-text는 adb shell am broadcast의 return code가 0이면 HTTP 200을 반환한다.
receiver가 property 때문에 무시했거나, ForegroundService가 실패했거나, Planner가
오류를 반환해도 최초 응답은 성공일 수 있다.
console은 이 응답을 transport_accepted로 표시하고 같은 request_id를 가진
lambda_response 또는 timeout까지 기다려야 한다. TaskManager 성공은 다시
workflow/task callback과 연결해야 한다.
P1. 연속 클릭 시 음성 전역 상태가 겹칠 수 있음
console의 버튼 lock은 ADB broadcast가 끝나면 즉시 해제된다. receiver는 현재 ForegroundService 상태를 admission 조건으로 사용하지 않는다. 서로 다른 발화는 2초 dedupe에도 걸리지 않는다.
겹칠 수 있는 값은 다음과 같다.
originalSttResultoneShotCloudTestTurnContextisCloudContinuemultiturnDelayJob- pending Cloud device task/workflow requests
다음 발화 허용 시점은 HTTP 200이 아니라 같은 request의 Cloud turn 종료 또는 명시적 취소여야 한다.
P1. trace가 여러 실행을 혼합
shim은 최대 200개 event를 process-global list에 저장하고 /trace는 since만
지원한다. device ID, session ID, request ID filter가 없다. 여러 batch나 브라우저가
같은 shim을 사용하면 Raw latest trace와 Planning panel이 다른 turn을 가리킬 수 있다.
P1. 실패 시 PAUSE 해제 보장이 약함
processVoiceCmd는 TimeoutCancellationException catch가 두 번 중복되어 있으며,
일반 예외를 받아 resetState와 sendStateResume을 보장하는 경로가 없다.
SupervisorJob은 다른 coroutine을 살릴 뿐 실패한 turn의 PAUSE를 복구하지 않는다.
이는 테스트 전용 문제가 아니라 실제 음성 경로에도 영향을 준다.
P2. text input의 ASR confidence가 이전 음성값을 재사용
실제 STT는 decode 때 lastAsrConfidence를 갱신한다. text path는 갱신하지 않으므로
기본값 또는 직전 음성 turn의 값을 Cloud에 보낸다. STT_NULL 축 회귀를 비교하려면
text mode의 명시적 confidence가 필요하다.
P2. direct Gemini mode는 운영 자격 검증을 증명하지 않음
--validation-mode direct_gemini는 local Planner 반복을 위해 terms, Cloud usage,
special device 검증의 내부 HTTP 호출을 건너뛴다. 이는 의도된 가상화이지만 이 모드의
통과를 운영 권한/자격 검증 통과로 기록하면 안 된다.
6. 확인된 정상 항목
- ADB shell command는
shlex.quote로 구성되어 발화의 shell metacharacter가 command로 해석되지 않는다. request_id와session_id는 receiver extra에서 one-shot test identity로 이어진다.- text voice mode는 TaskManager 실행 전
PAUSE와 기존 기기 상태 snapshot을 수행한다. - Cloud
voice_context에는 현재 device planning context가 포함된다. - Cloud device request는 TTS/session boundary까지 stage된 후 dispatch된다.
- TTS 완료 경로는
RESUME후 pending request를 실행한다. adb reverse tcp:18080 tcp:18080은 현재 기기에 설정되어 있다.
7. 릴리스 게이트
| 우선순위 | 조치 | 완료 증거 |
|---|---|---|
| P0 | shim 기본 bind를 127.0.0.1로 제한 |
runtime ss에서 loopback만 LISTEN |
| P0 | receiver를 signature/diagnostic build로 제한 | merged release manifest와 외부 broadcast 거부 |
| P1 | Cloud override를 request-scoped 또는 one-shot clear | text turn 후 실제 음성이 배포 endpoint 사용 |
| P1 | console target을 shim authoritative config로 표시 | /health target과 UI target 일치 |
| P1 | request correlation 완료 전 다음 전송 차단 | 동일/다른 발화 overlap 회귀 테스트 |
| P1 | trace device/session/request filter | 병렬 세션 trace 격리 테스트 |
| P1 | 모든 실패에서 RESUME 보장 | timeout/network/parser exception 회귀 테스트 |
| P2 | text ASR confidence 명시 | 이전 음성 turn과 독립된 payload 테스트 |
8. 현재 안전한 운영 절차
- shim은
--host 127.0.0.1과 명시적--adb-serial로 실행한다. - console의 ADB serial 입력값이 아니라 shim 시작 로그의 serial을 기준으로 삼는다.
/device-textHTTP 200 이후 같은 request의lambda_response를 확인하기 전에는 다음 발화를 보내지 않는다.- 실사용 음성 검증으로 돌아가기 전에 Cloud override property를 지우고 MaumAi process를 재시작해 in-memory override도 제거한다.
- 테스트 종료 시 text input enable property를 끈다.
- platform-signed APK를 사용하며 debug certificate APK는 system partition에 배포하지 않는다.