← Docs hub

MQTT Task Contract

이 문서는 IoT/MQTT에서 DeviceAgent TaskManager를 호출할 때 사용할 권장 메시지 계약이다. 목표는 MQTT, System App AAR, PUI, On-device가 서로 다른 표현을 쓰지 않고 같은 task 의미 체계를 공유하는 것이다.

한 문장 결론: broker, topic, 인증, QoS와 기존 단일 명령 payload를 일괄 교체하지 않는다. TaskManager 관리가 필요한 요청에만 request.task를 추가하고, 복합 실행은 별도의 공통 submitWorkflow ingress로 확장한다.

변경하지 않는 것 선택적으로 추가하는 것 별도 구현이 필요한 것
기존 MQTT 연결, Topic, 기능별 parser, 기존 direct command와 response 단일 명령의 request.task, 추적 ID와 Task event 소비 submitWorkflowpayload.subTasks[]를 Bundle 목록으로 변환하는 공통 parser

핵심 원칙:

MQTT가 task 의도를 명시하면 그 값을 우선 반영한다.
MQTT가 task 의도를 생략하면 기존 DeviceAgent direct command로 유지한다.
request.task가 있으면 TaskManager intent로 본다.
조회, heartbeat, sensor/status update는 기본적으로 task queue에 넣지 않는다.

관련 문서:

1. 전체 흐름

Cloud / IoT Platform
  -> MQTT topic/message
  -> IotAgent topic parser
  -> legacy DeviceAgent Bundle 생성
  -> request.task metadata를 Bundle top-level field로 복사
  -> IotTaskManagerBridge.wrapCommand()
  -> DeviceControl.sendModuleCommand()
  -> DeviceAgent.MainApi.executeMethod()
  -> direct legacy execution 또는 TaskManager.handleControlMethod()
  -> submitTask / submitWorkflow

현재 구현 기준:

IotAgent.setCmdTopic()
  -> IotTaskManagerBridge.setCurrentCommandJson(jsonObject)
  -> topic별 기존 parser 수행
  -> IotTaskManagerBridge.wrapCommand(...)
     - request.task를 읽어 Bundle field로 복사
     - request.task가 없으면 기존 method direct 호출 유지
     - request.task가 있으면 method=submitTask, taskMethod=기존 method, source=iot로 변환
     - managed=false면 direct 유지

구현 근거:

역할 소스 구현 포인트
MQTT command 진입 apps/IotAgent/.../CmdTopic.java setCmdTopic(topic, jsonObject) 시작 시 IotTaskManagerBridge.setCurrentCommandJson(jsonObject) 호출
MQTT command context 정리 apps/IotAgent/.../CmdTopic.java finally에서 IotTaskManagerBridge.clearCurrentCommandJson() 호출
task metadata 추출 apps/IotAgent/.../IotTaskManagerBridge.java currentTaskMetadata()request.task 또는 top-level task를 읽음
JSON -> Bundle 변환 apps/IotAgent/.../IotTaskManagerBridge.java toBundle(JSONObject taskMetadata)가 string/long/boolean field를 Bundle로 변환
기존 Bundle에 metadata 입히기 apps/IotAgent/.../IotTaskManagerBridge.java copyTaskMetadata(...)가 topic별 parser가 만든 legacy Bundle에 metadata를 복사
task wrapping apps/IotAgent/.../IotTaskManagerBridge.java wrapCommand(...)method=submitTask, taskMethod=기존 method, source=iot를 주입

이 방식은 기존 topic별 parser를 대량 수정하지 않기 위한 선택이다. CmdTopic 안에는 이미 많은 IotTaskManagerBridge.wrapCommand(...) 호출이 있으므로, MQTT 원문 JSON을 command 처리 scope 동안 ThreadLocal로 잡아두고 공통 wrapper가 request.task를 읽게 했다.

장점:
- 기존 setClean/comeback/moveTo/setSchedule parser를 대부분 유지한다.
- 모든 wrapCommand 호출이 같은 request.task 처리 규칙을 탄다.
- task metadata가 없는 기존 MQTT 메시지는 기존 direct command 동작을 유지한다.

주의:
- setCmdTopic 처리 thread 안에서만 현재 command JSON이 유효하다.
- finally에서 clear하지 않으면 다음 command에 metadata가 섞일 수 있으므로 clear는 필수다.

2. MQTT Envelope

모든 command topic은 아래 공통 envelope를 권장한다.

{
  "replyTo": "skmg/airbot/{serial}/{version}/response/{responseTopic}",
  "correlationId": "mqtt-request-001",
  "timestamp": "2026-07-15T10:00:00+09:00",
  "request": {
    "method": "setMoveTo",
    "payload": {},
    "task": {}
  }
}
필드 필수 설명 AAR/Bundle 매핑
replyTo 권장 응답 publish topic replyTo
correlationId 권장 MQTT 요청 추적 ID correlationId, traceId
timestamp 권장 요청 생성 시각 timestamp
request.method 필수 기존 DeviceAgent method 또는 submitWorkflow method 또는 taskMethod
request.payload 선택 실제 기능 parameter positionId, action, mode
request.task 선택 TaskManager 정책/완료/큐 metadata executionMode, priority, queueKey

3. Task Metadata

request.task는 optional이다. 없으면 기존 MQTT command 경로를 깨지 않기 위해 TaskManager에 등록하지 않고 direct legacy command로 보낸다. 값이 있으면 TaskManager intent로 해석하고, managed=false가 아닌 한 method=submitTask, taskMethod=<기존 method>, source=iot envelope로 변환한다.

즉 기본값은 아래처럼 나뉜다.

입력 형태 IotAgent routing DeviceAgent 처리
request.task 없음 direct 유지 기존 method 그대로 실행
request.task: {} TaskManager 등록 submitTask, 누락 정책은 TaskPolicyRegistry가 보강
request.task.managed=true TaskManager 등록 명시 metadata + 기본 정책 병합
request.task.managed=false direct 유지 metadata field는 복사될 수 있지만 task lifecycle에는 올리지 않음

request.task를 생략한 기존 명령은 관리 Task와 같은 대기열에 들어가지 않는다. 두 경로 모두 기존 DeviceAgent 도메인 정책을 통과하므로 이동·청정 중복과 제품 상태 제한은 적용되지만, TaskManager가 direct 명령의 순서나 선점 결과를 기록하는 것은 아니다.

따라서 관리 Workflow와 경쟁할 수 있는 이동·청정·보안·외부 앱 세션 명령은 검증된 범위부터 request.task를 붙이는 것이 권장된다. 기존 direct 호출을 계속 허용해야 하는 기간에는 공통 실행권 검사와 PUI·안전 중단 결과의 Task terminal 연결이 별도로 필요하다. 상세 기준은 기존 direct 명령과 관리 Task의 동시성을 따른다.

{
  "managed": true,
  "executionMode": "async",
  "priority": "HIGH",
  "queueKey": "movement",
  "timeoutMs": 240000,
  "completionTarget": "movement.arrived",
  "preemptPolicy": "append",
  "cancellable": true
}
MQTT field Bundle/AAR field 의미 생략 시 기본값
managed IotAgent routing hint true면 task 등록 우선, false면 direct 유지 request.task가 있으면 true, 없으면 direct
executionMode executionMode direct, async, queued_wait method별 TaskPolicyRegistry
priority priority BACKGROUND, NORMAL, CONTROL, HIGH, EMERGENCY method별 기본 priority
queueKey queueKey movement, cleaning, schedule, workflow method명 기반 기본 queue
timeoutMs timeoutMs task timeout method별 기본 timeout
completionTarget completionTarget 실제 완료 판단 target executor별 기본 target
completionAreaId completionAreaId 이동/청정 완료 대상 area id payload target에서 추론
preemptPolicy preemptPolicy 기존 queue 처리 방식 append
failurePolicy failurePolicy workflow step 실패 처리 fail_parent
cancellable cancellable cancel 허용 여부 method별 기본값
cancelMethod cancelMethod cancel 시 호출할 method queue별 기본 cancel method
compensationMethod compensationMethod 실패 보상 method method별 기본값
strictValidation strictValidation schema validation 강제 false
dryRun dryRun 실제 executor 대신 dry-run 흐름 false

3.1 구현된 복사 규칙

현재 IotTaskManagerBridgerequest.task에서 아래 타입별 field를 읽어 legacy Bundle top-level로 복사한다.

String field

executionMode
priority
queueKey
completionTarget
completionAreaId
preemptPolicy
failurePolicy
cancelMethod
compensationMethod

예:

{
  "request": {
    "method": "setMoveTo",
    "task": {
      "executionMode": "async",
      "priority": "HIGH",
      "queueKey": "movement",
      "completionTarget": "movement.arrived",
      "preemptPolicy": "append"
    }
  }
}

Bundle 반영:

executionMode=async
priority=HIGH
queueKey=movement
completionTarget=movement.arrived
preemptPolicy=append

Long field

timeoutMs

예:

{
  "request": {
    "method": "setMoveTo",
    "task": {
      "timeoutMs": 240000
    }
  }
}

Bundle 반영:

timeoutMs=240000

Boolean field

managed
cancellable
stateAware
strictValidation
dryRun

예:

{
  "request": {
    "method": "setMoveTo",
    "task": {
      "managed": true,
      "cancellable": true,
      "strictValidation": false
    }
  }
}

Bundle 반영:

managed=true
cancellable=true
strictValidation=false

3.2 managed 판단 순서

managed는 TaskManager wrapping 여부를 명시하는 field다.

request.task 없음
  -> 기존 MQTT direct command로 유지한다.

request.task 있음 + managed 생략
  -> request.task 자체를 명시적 task intent로 보고 managed=true처럼 처리한다.

managed=true
  -> method가 get/is/check/request가 아니어도 명시적으로 task 등록을 시도한다.

managed=false
  -> setMoveTo처럼 task 대상처럼 보이는 method도 direct legacy command로 유지한다.

이렇게 정한 이유:

1. 기존 MQTT payload를 바꾸지 않은 상태에서 갑자기 queue/task lifecycle에 올라가는 것을 막는다.
2. TaskManager를 쓰려는 요청은 request.task 존재만으로 의도가 드러난다.
3. AAR/System App도 submitTask를 명시 호출하므로 MQTT도 명시 intent 기준이 더 일관적이다.
4. setAirQuality, setEyeLedColor 같은 상시 신호가 monitor timeline을 오염시키는 것을 줄인다.

권장 판단표:

목적 MQTT 표현 결과
기존 동작 유지 request.task 생략 direct command
TaskManager 기본 정책 사용 "task": {} submitTask, method별 기본 정책
TaskManager 세부 정책 지정 "task": {"executionMode":"async", ...} submitTask, 명시값 우선
강제 direct "task": {"managed": false} direct command
명시 task 등록 "task": {"managed": true} submitTask

예시:

{
  "correlationId": "mqtt-direct-move-001",
  "request": {
    "method": "setMoveTo",
    "payload": {
      "positionId": "4"
    },
    "task": {
      "managed": false,
      "executionMode": "direct"
    }
  }
}

결과:

method=setMoveTo
positionId=4
executionMode=direct
traceId=mqtt-direct-move-001
correlationId=mqtt-direct-move-001

이 경우 method=submitTask, taskMethod=setMoveTo, source=iot는 붙지 않는다.

4. Execution Mode 의미

의미 사용 예
direct TaskManager control contract를 타더라도 queue 대기 없이 즉시 수행 조회성 또는 매우 짧은 상태 확인
async 등록 즉시 accepted/taskId를 반환하고 실제 완료는 event로 추적 이동, 복귀, 청정, LLM/TTS
queued_wait queue에 넣고 해당 호출은 완료/실패까지 기다림 설정 변경, 스케줄 DB 반영, 짧은 제어

권장:

장기 동작형: async
짧은 설정형: queued_wait
조회/상태 확인: direct 또는 task wrapping 제외

5. 단일 이동 예시

MQTT:

{
  "replyTo": "skmg/airbot/A1-board-005/v1/response/moveToResponse",
  "correlationId": "mqtt-move-001",
  "timestamp": "2026-07-15T10:00:00+09:00",
  "request": {
    "method": "setMoveTo",
    "payload": {
      "positionId": "4"
    },
    "task": {
      "managed": true,
      "executionMode": "async",
      "priority": "HIGH",
      "queueKey": "movement",
      "timeoutMs": 240000,
      "completionTarget": "movement.arrived",
      "preemptPolicy": "append"
    }
  }
}

DeviceAgent로 들어가는 Bundle:

method=submitTask
taskMethod=setMoveTo
source=iot
traceId=mqtt-move-001
correlationId=mqtt-move-001
replyTo=skmg/airbot/A1-board-005/v1/response/moveToResponse
positionId=4
executionMode=async
priority=HIGH
queueKey=movement
timeoutMs=240000
completionTarget=movement.arrived
preemptPolicy=append

처리 결과:

1. CmdTopic의 moveTo topic parser가 기존 방식대로 setMoveTo용 Bundle을 만든다.
2. IotTaskManagerBridge가 request.task를 읽어 executionMode/priority/queueKey/timeoutMs/completionTarget/preemptPolicy를 복사한다.
3. managed=true이므로 method를 submitTask로 바꾸고 기존 method를 taskMethod=setMoveTo로 보존한다.
4. DeviceAgent TaskManager는 explicit metadata를 우선 반영하고 누락값은 TaskPolicyRegistry 기본값으로 채운다.

6. 스테이션 복귀 예시

{
  "replyTo": "skmg/airbot/A1-board-005/v1/response/comebackResponse",
  "correlationId": "mqtt-return-001",
  "timestamp": "2026-07-15T10:05:00+09:00",
  "request": {
    "method": "returnToStation",
    "payload": {
      "missionFinYn": "Y"
    },
    "task": {
      "managed": true,
      "executionMode": "async",
      "priority": "HIGH",
      "queueKey": "movement",
      "timeoutMs": 240000,
      "completionTarget": "movement.docked",
      "preemptPolicy": "replace_queue"
    }
  }
}

replace_queue는 이동 queue의 기존 task를 취소할 수 있으므로 IoT source에서 허용할 method를 제한해야 한다.

Bundle 핵심:

method=submitTask
taskMethod=returnToStation
source=iot
missionFinYn=Y
executionMode=async
priority=HIGH
queueKey=movement
timeoutMs=240000
completionTarget=movement.docked
preemptPolicy=replace_queue

권장 운영:

복귀 명령은 사용자가 현재 이동/청정 흐름을 중단하려는 의미일 수 있으므로 replace_queue를 허용할 수 있다.
다만 모든 IoT 명령에 replace_queue를 허용하면 기존 workflow를 예기치 않게 끊을 수 있다.

7. 청정 시작 예시

{
  "replyTo": "skmg/airbot/A1-board-005/v1/response/setCleanResponse",
  "correlationId": "mqtt-clean-001",
  "timestamp": "2026-07-15T10:10:00+09:00",
  "request": {
    "method": "setAirCleanerOperation",
    "payload": {
      "module": "app",
      "action": "1",
      "mode": "1",
      "speed": "0",
      "position": ["4"],
      "cur_destination": "4"
    },
    "task": {
      "managed": true,
      "executionMode": "async",
      "priority": "CONTROL",
      "queueKey": "cleaning",
      "timeoutMs": 1200000,
      "completionTarget": "cleaning.stopped",
      "completionAreaId": "4"
    }
  }
}

Bundle 핵심:

method=submitTask
taskMethod=setAirCleanerOperation
source=iot
module=app
action=1
mode=1
speed=0
position=[4]
cur_destination=4
executionMode=async
priority=CONTROL
queueKey=cleaning
timeoutMs=1200000
completionTarget=cleaning.stopped
completionAreaId=4

설명:

청정 동작은 command return만으로 완료 처리하면 안 된다.
completionTarget=cleaning.stopped 또는 domain callback/post-check를 통해 실제 완료 시점을 판단해야 한다.

8. Direct / Task 제외 예시

상시 상태 update나 조회성 명령은 task queue에 넣지 않는 것이 기본이다.

{
  "correlationId": "mqtt-status-001",
  "request": {
    "method": "setAirQuality",
    "payload": {
      "pm10": "12",
      "pm25": "5"
    },
    "task": {
      "managed": false
    }
  }
}

결과:

method=setAirQuality
traceId=mqtt-status-001
correlationId=mqtt-status-001
pm10=12
pm25=5

setAirQuality, heartbeat, healthCheck, AWS connection state, LED 상시 상태처럼 monitor 노이즈가 큰 값은 기본적으로 task lifecycle에 올리지 않는다.

8.1 request.task 생략 예시

기존 MQTT payload가 request.task 없이 내려오면 TaskManager에 등록하지 않고 기존 direct command로 동작한다.

{
  "replyTo": "skmg/airbot/A1-board-005/v1/response/moveToResponse",
  "correlationId": "mqtt-move-default-001",
  "timestamp": "2026-07-15T10:20:00+09:00",
  "request": {
    "method": "setMoveTo",
    "payload": {
      "positionId": "4"
    }
  }
}

Bundle 핵심:

method=setMoveTo
positionId=4
traceId=mqtt-move-default-001
correlationId=mqtt-move-default-001

이 경우 TaskManager task id, queue event, completion event는 생성되지 않는다. 기존 MQTT command와 동일하게 DeviceAgent method가 직접 실행된다.

8.2 빈 request.task로 TaskManager 기본 정책 사용

TaskManager에는 올리고 싶지만 세부 정책은 DeviceAgent 기본값을 쓰고 싶으면 빈 task object를 보낸다.

{
  "replyTo": "skmg/airbot/A1-board-005/v1/response/moveToResponse",
  "correlationId": "mqtt-move-policy-default-001",
  "timestamp": "2026-07-15T10:21:00+09:00",
  "request": {
    "method": "setMoveTo",
    "payload": {
      "positionId": "4"
    },
    "task": {}
  }
}

Bundle 핵심:

method=submitTask
taskMethod=setMoveTo
source=iot
positionId=4
managed=true
traceId=mqtt-move-policy-default-001
correlationId=mqtt-move-policy-default-001

TaskManager 기본 정책 예:

setMoveTo -> executionMode=ASYNC, queue=movement, priority=HIGH, timeoutMs=120000, cancellable=true

즉, 외부가 모든 정책을 매번 내려줄 필요는 없다. 다만 TaskManager를 쓰겠다는 의도는 request.task object로 명시해야 한다.

8.3 Strict Validation 예시

schema 검증을 강제로 켜고 싶으면 strictValidation=true를 내려준다.

{
  "correlationId": "mqtt-strict-001",
  "request": {
    "method": "setMoveTo",
    "payload": {},
    "task": {
      "managed": true,
      "strictValidation": true
    }
  }
}

의도:

setMoveTo는 positionId/positionName/position/positionIds 중 하나가 필요하다.
strictValidation=true이면 누락 시 TaskManager가 validation_failed로 reject해야 한다.

9. 복합 Workflow 예시

외부에서 workflow를 직접 내릴 때는 request.method=submitWorkflow를 사용한다. payload.subTasks[] 안의 각 항목은 AAR TaskWorkflowRequest의 subtask와 같은 의미다.

{
  "replyTo": "skmg/airbot/A1-board-005/v1/response/workflowResponse",
  "correlationId": "mqtt-workflow-001",
  "timestamp": "2026-07-15T10:15:00+09:00",
  "request": {
    "method": "submitWorkflow",
    "payload": {
      "workflowName": "room4_room2_clean_return",
      "subTasks": [
        {
          "taskMethod": "setMoveTo",
          "positionId": "4",
          "completionTarget": "movement.arrived",
          "timeoutMs": 240000
        },
        {
          "taskMethod": "setAirCleanerOperation",
          "module": "app",
          "action": "1",
          "mode": "1",
          "speed": "0",
          "position": ["4"],
          "cur_destination": "4",
          "completionTarget": "cleaning.stopped",
          "completionAreaId": "4",
          "timeoutMs": 1200000
        },
        {
          "taskMethod": "setMoveTo",
          "positionId": "2",
          "completionTarget": "movement.arrived",
          "timeoutMs": 240000
        },
        {
          "taskMethod": "setAirCleanerOperation",
          "module": "app",
          "action": "1",
          "mode": "1",
          "speed": "0",
          "position": ["2"],
          "cur_destination": "2",
          "completionTarget": "cleaning.stopped",
          "completionAreaId": "2",
          "timeoutMs": 1200000
        },
        {
          "taskMethod": "returnToStation",
          "completionTarget": "movement.docked",
          "timeoutMs": 240000
        }
      ]
    },
    "task": {
      "managed": true,
      "executionMode": "async",
      "priority": "HIGH",
      "queueKey": "workflow",
      "preemptPolicy": "append",
      "failurePolicy": "fail_parent"
    }
  }
}

주의:

현재 단일 command metadata 파싱은 구현되어 있다.
MQTT JSON의 submitWorkflow payload.subTasks를 ArrayList<Bundle>로 변환하는 topic handler는 별도 구현 범위다.

구현 계획:

1. request.method == submitWorkflow 감지
2. request.payload.workflowName을 Bundle.workflowName으로 복사
3. request.payload.subTasks[]를 ArrayList<Bundle>로 변환
4. request.task metadata를 workflow parent Bundle에 복사
5. method=submitWorkflow, source=iot, traceId/correlationId/replyTo 주입
6. DeviceControl.sendModuleCommand(indata, outdata) 호출

workflow parser가 구현되기 전에는 기존 topic별 단일 command 경로에 request.task metadata를 붙이는 방식만 보장한다.

10. AAR 계약과의 동일성

MQTT request.task는 AAR builder의 아래 호출과 같은 의미를 가져야 한다.

TaskSubmitRequest.builder(TaskMethods.SET_MOVE_TO)
        .source(TaskSources.IOT)
        .requestId("mqtt-move-001")
        .traceId("mqtt-move-001")
        .putString(TaskParams.POSITION_ID, "4")
        .executionMode(TaskExecutionModes.ASYNC)
        .priority(TaskPriorities.HIGH)
        .queue(TaskQueues.MOVEMENT)
        .timeoutMs(240_000)
        .completionTarget(TaskCompletionTargets.MOVEMENT_ARRIVED)
        .preemptPolicy(TaskPreemptPolicies.APPEND)
        .build();

동일성 기준:

의미 MQTT AAR / Bundle
실행할 기존 method request.method 또는 subTasks[].taskMethod TaskMethods.*, taskMethod
호출 source fixed by IotAgent TaskSources.IOT, source=iot
request 추적 correlationId requestId, traceId, correlationId
기능 parameter request.payload.* TaskParams.*
실행 방식 request.task.executionMode TaskExecutionModes.*, executionMode
queue request.task.queueKey TaskQueues.*, queueKey
완료 기준 request.task.completionTarget TaskCompletionTargets.*, completionTarget
선점 정책 request.task.preemptPolicy TaskPreemptPolicies.*, preemptPolicy

10.1 System App AAR와 MQTT의 같은 요청 비교

System App AAR:

TaskSubmitRequest request = TaskSubmitRequest.builder(TaskMethods.SET_MOVE_TO)
        .source(TaskSources.IOT)
        .requestId("mqtt-move-001")
        .traceId("mqtt-move-001")
        .putString(TaskParams.POSITION_ID, "4")
        .executionMode(TaskExecutionModes.ASYNC)
        .priority(TaskPriorities.HIGH)
        .queue(TaskQueues.MOVEMENT)
        .timeoutMs(240_000)
        .completionTarget(TaskCompletionTargets.MOVEMENT_ARRIVED)
        .preemptPolicy(TaskPreemptPolicies.APPEND)
        .build();

MQTT:

{
  "correlationId": "mqtt-move-001",
  "request": {
    "method": "setMoveTo",
    "payload": {
      "positionId": "4"
    },
    "task": {
      "executionMode": "async",
      "priority": "HIGH",
      "queueKey": "movement",
      "timeoutMs": 240000,
      "completionTarget": "movement.arrived",
      "preemptPolicy": "append"
    }
  }
}

위 MQTT 예시는 managed를 생략했지만 request.task object가 존재하므로 managed=true와 같은 TaskManager intent로 처리한다.

TaskManager가 받는 의미:

둘 다 같은 taskMethod=setMoveTo 실행 요청이다.
둘 다 movement queue에 async로 등록된다.
둘 다 movement.arrived를 완료 기준으로 본다.
둘 다 traceId/correlationId로 추적 가능해야 한다.

11. 제한해야 하는 값

MQTT가 내려주는 task metadata를 그대로 신뢰하면 안 된다.

Field 제한 이유 권장 제한
priority=EMERGENCY 외부 source가 긴급 task를 남발할 수 있음 IoT는 기본 거부 또는 allowlist method만 허용
preemptPolicy=replace_all 전체 queue 취소 위험 운영 명령/관리자 명령만 허용
preemptPolicy=replace_queue 현재 이동/청정 task 취소 위험 return/stop 계열 allowlist
timeoutMs 과대값 queue 장시간 점유 method별 상한 clamp
queueKey 임의값 queue 격리 파괴 known queue만 허용
cancelMethod 임의 method 호출 위험 TaskPolicyRegistry 기본값 우선
compensationMethod 임의 보상 호출 위험 allowlist만 허용

현재 구현상 request.task field를 Bundle로 복사하는 것까지는 되어 있다. 위 제한 정책 중 일부는 DeviceAgent TaskCallerPolicy, TaskPolicyRegistry, TaskResourcePolicy에서 처리되고, IoT source 전용 clamp/allowlist는 추가 보강 대상이다.

11.1 테스트 근거

현재 focused unit test는 아래를 검증한다.

테스트 확인 내용
wrapCommand_withoutTaskMetadata_shouldKeepLegacyCommandDirect request.task가 없으면 setAirCleanerOperation도 기존 direct method로 유지되는지
wrapCommand_whenTaskMetadataIsManaged_shouldConvertToIotSubmitTaskEnvelope managed=true metadata가 있으면 legacy Bundle이 submitTask/taskMethod/source=iot로 감싸지는지
wrapCommand_whenRoutineOrStatusMethod_shouldKeepLegacyCommand setAirQuality, setEyeLedColor, event 등 상시/상태 신호는 direct 유지되는지
wrapCommand_whenTaskMetadataPresent_shouldCopyPolicyFields managed, executionMode, priority, queueKey, timeoutMs, completionTarget, preemptPolicy, cancellable이 Bundle로 복사되는지
wrapCommand_whenManagedFalse_shouldKeepDirectEvenForTaskLikeMethod managed=false이면 setMoveTosubmitTask로 감싸지 않는지
CmdTopicTaskManagerRoutingTest CmdTopic의 DeviceAgent 호출이 IotTaskManagerBridge.wrapCommand(...)를 통과하는지

검증 명령:

cd ~/work/1.A1_SoC_new/SoC/a1-packages/apps/IotAgent
ANDROID_HOME=/home/silogood/Android/Sdk ANDROID_SDK_ROOT=/home/silogood/Android/Sdk \
./gradlew testDevDebugUnitTest \
  --tests 'com.sk.airbot.iotagent.mqtt.topic.IotTaskManagerBridgeTest' \
  --tests 'com.sk.airbot.iotagent.mqtt.topic.CmdTopicTaskManagerRoutingTest'

검증 결과:

BUILD SUCCESSFUL

12. 타팀 공유용 요약

MQTT는 기존 command schema를 유지해도 된다.
TaskManager 의도가 없으면 request.task를 생략하고 기존 direct command로 둔다.
TaskManager 의도가 필요한 경우 request.task에 공통 metadata를 넣는다.
request.task가 비어 있어도 TaskManager 기본 정책을 쓰겠다는 명시 intent로 본다.
IotAgent는 request.task를 AAR/Bundle field와 같은 이름으로 복사한다.
DeviceAgent는 명시 metadata를 허용 범위 내에서 반영하고, 없는 값은 기본 정책으로 채운다.
외부 앱 AAR, MQTT, PUI, On-device는 모두 taskMethod/source/executionMode/queueKey/completionTarget이라는 같은 언어를 사용한다.

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