Selaa lähdekoodia

fix(ai-terminal): close device policy and contract gaps

- IntentClassifier.blockedIfNeeded: keyword route now checked against scene allowedAgents
  (previously only DeepSeek path was constrained; robot could still trigger REGISTRATION)
- DeviceRegistryFacadeImpl.scene(): reject pending/rejected devices — return null to block core flow
- OpenAPI schema alignment:
  ChatMessage.content→text, DeviceRegisterData status→activateStatus,
  DeviceSceneData uiTemplate→homeTemplate etc., DeviceEventRequest fields aligned
- OpenAPI HmacAuth security scheme: fix name from Authorization to X-Emoon-Access-Key
- Remove orphaned AgentBinding and NearbyDetectedPayload schemas
WangKang 1 viikko sitten
vanhempi
commit
f1d31bb2e9

+ 75 - 82
docs/接口文档/emoon-ai-openplatform-api-v1.2.openapi.yaml

@@ -2240,8 +2240,8 @@ components:
     HmacAuth:
       type: apiKey
       in: header
-      name: Authorization
-      description: 格式:EMOON-HMAC <access_key>;同时必须携带 X-Emoon-Timestamp、X-Emoon-Nonce、X-Emoon-Signature
+      name: X-Emoon-Access-Key
+      description: 项目 Access Key。同时必须携带 X-Emoon-Timestamp(Unix 毫秒)、X-Emoon-Nonce(随机 UUID)、X-Emoon-Signature(Base64(HMAC-SHA256))
   parameters:
     ProjectId:
       name: X-Emoon-Project-Id
@@ -2673,7 +2673,7 @@ components:
       type: object
       required:
       - type
-      - content
+      - text
       properties:
         type:
           type: string
@@ -2682,14 +2682,15 @@ components:
           - image
           - audio
           - file
-        content:
+        text:
           type: string
-          description: 文本内容或 fileId
+          description: 文本内容(type=text 时必填)
         fileId:
           type: string
-          description: 多模态文件推荐传 fileId。
+          description: 多模态文件推荐传 fileId(type=image/audio/file 时使用)
         clientMessageId:
           type: string
+          description: 客户端消息幂等 ID。
     AgentChatRequest:
       type: object
       required:
@@ -3034,17 +3035,24 @@ components:
           type: string
         deviceSecret:
           type: string
+        activateStatus:
+          type: string
+          description: 设备准入状态。activated 可进入核心业务,pending 待人工审批,rejected 已拒绝。
+          enum:
+          - activated
+          - pending
+          - rejected
         admissionLevel:
           type: string
+          description: 设备等级 A/B/C/D。
           enum:
           - A
           - B
           - C
           - D
-        defaultSceneCode:
-          type: string
-        status:
+        reason:
           type: string
+          description: 当 activateStatus 为 pending 或 rejected 时,说明原因。
     ApiResponseDeviceRegister:
       allOf:
       - $ref: '#/components/schemas/ApiResponse'
@@ -3108,33 +3116,37 @@ components:
       properties:
         deviceId:
           type: string
+        deviceType:
+          type: string
+          description: 设备类型 robot/self_service_kiosk/guide_screen 等。
         sceneCode:
           type: string
-        uiTemplate:
+        homeTemplate:
+          type: string
+          description: 首页 UI 模板标识。
+        defaultAgent:
           type: string
-        agentBindings:
+          description: 默认智能体代码。
+        allowedAgents:
           type: array
           items:
-            $ref: '#/components/schemas/AgentBinding'
-        toolScopes:
+            type: string
+          description: 该设备在当前场景下允许调用的智能体列表。
+        blockedIntents:
           type: array
           items:
             type: string
-        features:
-          type: object
-          additionalProperties: true
-        expiresAt:
-          type: string
-          format: date-time
-    AgentBinding:
-      type: object
-      properties:
-        agentId:
-          type: string
-        role:
-          type: string
-        default:
-          type: boolean
+          description: 该设备在当前场景下被阻止的意图列表。
+        capabilities:
+          type: array
+          items:
+            type: string
+          description: 设备硬件能力列表(如 touch/camera/id_card_ocr/file_upload)。
+        allowedCards:
+          type: array
+          items:
+            type: string
+          description: 该设备在当前场景下允许渲染的卡片类型列表。
     ApiResponseDeviceScene:
       allOf:
       - $ref: '#/components/schemas/ApiResponse'
@@ -3146,80 +3158,61 @@ components:
       properties:
         sceneCode:
           type: string
-        uiTemplate:
+        homeTemplate:
           type: string
-        agentBindings:
+          description: 首页 UI 模板标识。
+        allowedAgents:
+          type: array
+          items:
+            type: string
+          description: 允许调用的智能体列表。
+        allowedCards:
           type: array
           items:
-            $ref: '#/components/schemas/AgentBinding'
+            type: string
+          description: 允许渲染的卡片类型列表。
+        blockedIntents:
+          type: array
+          items:
+            type: string
+          description: 阻止的意图列表。
         effectiveAt:
           type: string
           format: date-time
     DeviceEventRequest:
       type: object
       required:
-      - clientEventId
+      - eventId
       - eventType
       - occurredAt
       properties:
-        clientEventId:
+        eventId:
           type: string
+          description: 设备端生成的唯一事件 ID。
         eventType:
           type: string
+          description: 设备事件类型。
           enum:
-          - qr_scanned
-          - card_read
-          - print_completed
-          - print_failed
-          - nearby_detected
-          - navigation_started
-          - navigation_completed
-          - device_error
-          - user_interaction
-          - custom
+          - QR_SCANNED
+          - CARD_READ
+          - PRINT_COMPLETED
+          - PRINT_FAILED
+          - NEARBY_DETECTED
+          - NAVIGATION_STARTED
+          - NAVIGATION_COMPLETED
+          - DEVICE_ERROR
+          - TOUCH_INTERACTION
+          - CUSTOM
+        eventPayload:
+          type: object
+          additionalProperties: true
+          description: 事件附加数据(如 screen/action 等)。
         occurredAt:
           type: string
           format: date-time
-        payload:
-          oneOf:
-          - $ref: '#/components/schemas/NearbyDetectedPayload'
-          - type: object
-            additionalProperties: true
-        billingContext:
-          $ref: '#/components/schemas/BillingContext'
-    NearbyDetectedPayload:
-      type: object
-      properties:
-        subject:
-          type: object
-          properties:
-            type:
-              type: string
-              enum:
-              - temporary_token
-              - ble_device_id
-              - nfc_token
-              - qr_session
-            value:
-              type: string
-        signal:
-          type: object
-          properties:
-            type:
-              type: string
-              enum:
-              - ble
-              - nfc
-              - wifi
-              - harmony
-              - qrcode
-            rssi:
-              type: integer
-            confidence:
-              type: number
-              format: float
-        areaCode:
+        traceId:
           type: string
+          description: 可选链路追踪 ID。
     DeviceCommand:
       type: object
       properties:

+ 8 - 0
emoon-infra/emoon-modules/emoon-ai/emoon-ai-agent/src/main/java/com/emoon/ai/agent/application/IntentClassifier.java

@@ -113,6 +113,14 @@ public class IntentClassifier {
                                            List<String> allowedAgents) {
         if (decision.blocked()) return decision;
 
+        // Device policy: enforce scene-level allowedAgents whitelist (both keyword and DeepSeek paths)
+        if (allowedAgents != null && !allowedAgents.isEmpty()
+                && decision.routeAgentCode() != null
+                && !allowedAgents.contains(decision.routeAgentCode())) {
+            log.warn("[Intent] routeAgentCode {} blocked by allowedAgents {}", decision.routeAgentCode(), allowedAgents);
+            return RouteDecision.blocked("当前设备不支持该功能");
+        }
+
         // Device policy: guide screen blocks high-privacy actions
         if ("guide_screen".equals(deviceType)) {
             String ic = decision.intentCode();

+ 6 - 0
emoon-infra/emoon-modules/emoon-ai/emoon-ai-device/src/main/java/com/emoon/ai/device/application/DeviceRegistryFacadeImpl.java

@@ -109,6 +109,12 @@ public class DeviceRegistryFacadeImpl implements DeviceRegistryFacade {
         if (device == null) {
             return null;
         }
+        // Reject non-activated devices: pending/rejected cannot enter core flow
+        String deviceStatus = device.getStatus();
+        if (!"activated".equals(deviceStatus) && !"online".equals(deviceStatus)) {
+            log.warn("[Device Scene] device {} status={} — blocked from core flow", deviceId, deviceStatus);
+            return null;
+        }
         DeviceSceneBindingDO binding = sceneBindingMapper.selectByDeviceId(deviceId);
         if (binding == null) {
             // Return minimal profile — device is registered but no scene bound