← Docs hub

A2A 테스트 진입점과 운영 음성 경로 정합성 감사

기준 시점은 2026-07-27이다. 이 문서는 이미 완료한 Cloud Planner, DeviceAgent TaskManager, IotAgent, Task Monitor 메서드 계약 감사를 반복하지 않는다. A2A Text Console -> local Lambda shim -> TextInputTestReceiver -> ForegroundService -> Cloud A2A Planner가 실제 음성 세션을 어느 지점부터 동일하게 재현하는지와, 테스트 경로가 운영 상태를 오염시키는 지점을 별도로 검증한다.

1. 결론

현재 /device-textSTT가 끝난 이후의 음성 처리 경로를 상당 부분 재사용한다. PAUSE, 기기 상태 snapshot, processVoiceCmd, Cloud request, TTS, RESUME, TTS 종료 후 TaskManager dispatch까지는 실제 코드를 탄다.

그러나 아래 항목 때문에 전체 음성 E2E와 동일하다고 판정할 수 없다.

따라서 현재 판정은 다음과 같다.

검증 범위 판정
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-textadb 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를 인증 없이 연다.

현재 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을 주입할 수 있다.

완료 기준은 아래 중 하나다.

  1. 진단 전용 product flavor에서만 component를 포함한다.
  2. platform signature permission으로 보호하고 호출자까지 검증한다.
  3. 최소한 테스트 종료 시 component/property를 자동 비활성화한다.

P1. UI의 기기 선택값과 실제 대상이 다름

console은 adb_serialdeviceId를 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-textadb 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에도 걸리지 않는다.

겹칠 수 있는 값은 다음과 같다.

다음 발화 허용 시점은 HTTP 200이 아니라 같은 request의 Cloud turn 종료 또는 명시적 취소여야 한다.

P1. trace가 여러 실행을 혼합

shim은 최대 200개 event를 process-global list에 저장하고 /tracesince만 지원한다. device ID, session ID, request ID filter가 없다. 여러 batch나 브라우저가 같은 shim을 사용하면 Raw latest trace와 Planning panel이 다른 turn을 가리킬 수 있다.

P1. 실패 시 PAUSE 해제 보장이 약함

processVoiceCmdTimeoutCancellationException catch가 두 번 중복되어 있으며, 일반 예외를 받아 resetStatesendStateResume을 보장하는 경로가 없다. 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. 확인된 정상 항목

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. 현재 안전한 운영 절차

  1. shim은 --host 127.0.0.1과 명시적 --adb-serial로 실행한다.
  2. console의 ADB serial 입력값이 아니라 shim 시작 로그의 serial을 기준으로 삼는다.
  3. /device-text HTTP 200 이후 같은 request의 lambda_response를 확인하기 전에는 다음 발화를 보내지 않는다.
  4. 실사용 음성 검증으로 돌아가기 전에 Cloud override property를 지우고 MaumAi process를 재시작해 in-memory override도 제거한다.
  5. 테스트 종료 시 text input enable property를 끈다.
  6. platform-signed APK를 사용하며 debug certificate APK는 system partition에 배포하지 않는다.

9. 관련 문서

Keyboard shortcuts

⌘K / Ctrl+KOpen command palette
/Focus search
g hGo to home
g pGo to projects
g sGo to sessions
j / kNext / prev row (tables)
?Show this help
EscClose dialogs

Structured queries

Mix key:value filters with free text in the palette:

type:sessionOnly session pages
project:llm-wikiFilter by project name (substring)
model:claudeFilter by model name (substring)
date:>2026-03-01Sessions after a date
date:<2026-04-01Sessions before a date
tags:rustPages mentioning a tag/topic
sort:dateSort results by date (newest first)

Example: type:session project:llm-wiki date:>2026-04 sort:date