{
  "feature": "palm-vein",
  "version": "2.0.0",
  "description": "USB palm vein scanner integration using SDPVUnifiedAPI — capture, ROI detection, enrollment, 1:N identification, and cache pool management",
  "category": "integration",
  "tags": [
    "biometric",
    "vein-pattern",
    "hardware",
    "sdk",
    "usb",
    "android"
  ],
  "actors": [
    {
      "id": "host_application",
      "name": "Host Application",
      "type": "system",
      "description": "Android terminal app that calls the SDK API to perform palm vein operations",
      "role": "integration"
    },
    {
      "id": "palm_scanner",
      "name": "PVM310 Biometric Scanner",
      "type": "external",
      "description": "USB-connected palm vein scanning hardware (vendor-id 31109, product-id 4097)",
      "role": "biometric-capture"
    },
    {
      "id": "usb_manager",
      "name": "USB Permission Manager",
      "type": "system",
      "description": "Android USB host permission handler (PalmUSBManager) in the Application class",
      "role": "usb-permission"
    }
  ],
  "fields": [
    {
      "name": "license_path",
      "type": "text",
      "required": true,
      "label": "License File Path",
      "default": "/sdcard/SD_TEMPLATE/LICENSE/license.dat",
      "validation": [
        {
          "type": "required",
          "message": "License file path is required for SDK service initialization"
        }
      ]
    },
    {
      "name": "chip_type",
      "type": "text",
      "required": true,
      "label": "Target Chip Platform",
      "default": "RK3568"
    },
    {
      "name": "proximity_intensity_max",
      "type": "number",
      "required": true,
      "label": "Proximity Intensity Threshold",
      "default": 500,
      "validation": [
        {
          "type": "min",
          "value": 100,
          "message": "Intensity threshold must be at least 100"
        },
        {
          "type": "max",
          "value": 1000,
          "message": "Intensity threshold must not exceed 1000"
        }
      ]
    },
    {
      "name": "firmware_version",
      "type": "text",
      "required": false,
      "label": "Firmware Version"
    },
    {
      "name": "serial_number",
      "type": "text",
      "required": false,
      "label": "Device Serial Number"
    },
    {
      "name": "usb_vendor_id",
      "type": "number",
      "required": true,
      "label": "USB Vendor ID",
      "default": 31109
    },
    {
      "name": "usb_product_id",
      "type": "number",
      "required": true,
      "label": "USB Product ID",
      "default": 4097
    },
    {
      "name": "palm_template",
      "type": "json",
      "required": false,
      "label": "Palm Vein Template",
      "sensitive": true
    },
    {
      "name": "palm_token",
      "type": "text",
      "required": false,
      "label": "Palm Cache Token"
    },
    {
      "name": "palm_roi_image",
      "type": "json",
      "required": false,
      "label": "Palm ROI Image",
      "sensitive": true
    },
    {
      "name": "palm_raw_image",
      "type": "json",
      "required": false,
      "label": "Palm Raw Image",
      "sensitive": true
    },
    {
      "name": "palm_hand",
      "type": "select",
      "required": false,
      "label": "Enrolled Hand",
      "options": [
        {
          "value": "left",
          "label": "Left Hand"
        },
        {
          "value": "right",
          "label": "Right Hand"
        }
      ]
    },
    {
      "name": "timeout_seconds",
      "type": "number",
      "required": true,
      "label": "Operation Timeout",
      "default": 30,
      "validation": [
        {
          "type": "min",
          "value": 1,
          "message": "Timeout must be at least 1 second"
        },
        {
          "type": "max",
          "value": 120,
          "message": "Timeout should not exceed 120 seconds"
        }
      ]
    },
    {
      "name": "image_masked",
      "type": "boolean",
      "required": true,
      "label": "Apply Mask to Display Image",
      "default": true
    },
    {
      "name": "led_color",
      "type": "select",
      "required": false,
      "label": "LED Color",
      "options": [
        {
          "value": "LED_RED",
          "label": "Red"
        },
        {
          "value": "LED_GREEN",
          "label": "Green"
        },
        {
          "value": "LED_BLUE",
          "label": "Blue"
        }
      ]
    },
    {
      "name": "led_duration_ms",
      "type": "number",
      "required": false,
      "label": "LED Duration (ms)",
      "default": 1000
    },
    {
      "name": "cache_pool_count",
      "type": "number",
      "required": false,
      "label": "Palms in Cache Pool"
    }
  ],
  "states": {
    "field": "device_state",
    "values": [
      {
        "id": "uninitialized",
        "label": "Uninitialized",
        "initial": true
      },
      {
        "id": "cache_pool_ready",
        "label": "Cache Pool Ready"
      },
      {
        "id": "service_ready",
        "label": "Service Ready"
      },
      {
        "id": "usb_waiting",
        "label": "Waiting for USB Permission"
      },
      {
        "id": "device_open",
        "label": "Device Open"
      },
      {
        "id": "idle",
        "label": "Ready / Idle"
      },
      {
        "id": "capturing",
        "label": "Capturing Images"
      },
      {
        "id": "enrolling",
        "label": "Enrolling"
      },
      {
        "id": "identifying",
        "label": "Identifying"
      },
      {
        "id": "device_closed",
        "label": "Device Closed",
        "terminal": true
      }
    ],
    "transitions": [
      {
        "from": "uninitialized",
        "to": "cache_pool_ready",
        "actor": "host_application",
        "description": "Call initCachePool(context, ChipType.RK3568) at app start"
      },
      {
        "from": "cache_pool_ready",
        "to": "service_ready",
        "actor": "host_application",
        "description": "Call initService(licensePath) then initModel() to load the algorithm"
      },
      {
        "from": "service_ready",
        "to": "usb_waiting",
        "actor": "usb_manager",
        "description": "PalmUSBManager requests USB permission from Android system"
      },
      {
        "from": "usb_waiting",
        "to": "device_open",
        "actor": "usb_manager",
        "description": "USB permission granted — onCheckPermission(0) received"
      },
      {
        "from": "device_open",
        "to": "idle",
        "actor": "host_application",
        "description": "initDevice(context) returns RETURN_DEVICE_SUCCESS"
      },
      {
        "from": "idle",
        "to": "capturing",
        "actor": "host_application",
        "description": "Start captureImage() loop for extraction or enrollment"
      },
      {
        "from": "capturing",
        "to": "enrolling",
        "actor": "host_application",
        "description": "ROI detected — enroll(EnrollPicture) called, PalmEnroll listener active"
      },
      {
        "from": "capturing",
        "to": "identifying",
        "actor": "host_application",
        "description": "ROI detected — identifyFeature(roiImage, callback) called"
      },
      {
        "from": "enrolling",
        "to": "idle",
        "actor": "palm_scanner",
        "description": "Enrollment completes via enrollComplete() or enrollFail() callback"
      },
      {
        "from": "identifying",
        "to": "idle",
        "actor": "palm_scanner",
        "description": "Identification completes via identify callback"
      },
      {
        "from": "capturing",
        "to": "idle",
        "actor": "host_application",
        "description": "Capture loop stopped (timeout, cancel, or error)"
      },
      {
        "from": "idle",
        "to": "device_closed",
        "actor": "host_application",
        "description": "Call terminateDevice()"
      },
      {
        "from": "device_open",
        "to": "device_closed",
        "actor": "usb_manager",
        "description": "USB device physically removed — onUSBRemoved() fires"
      }
    ]
  },
  "rules": {
    "sdk_singleton": {
      "api_access": "SDPVUnifiedAPI.getInstance() returns the singleton — all operations go through this instance",
      "thread_safety": "captureImage(), detectRoi(), enroll(), identifyFeature() must run on background threads — never on the main thread"
    },
    "initialization_sequence": {
      "order": "initCachePool → initService → initModel → (USB permission) → initDevice",
      "cache_pool_first": "initCachePool is time-consuming and must complete before any other SDK call",
      "chip_type": "ChipType.RK3568 is required for the PVM310 terminal hardware",
      "license_copy": "License file must exist at /sdcard/SD_TEMPLATE/LICENSE/license.dat — app copies from res/raw/license.dat on first launch",
      "model_after_service": "initModel() must be called after initService() to load the deep learning algorithm"
    },
    "usb_permission": {
      "handler": "PalmUSBManager created in Application.onCreate() handles USB attach/detach/permission",
      "auto_request": "USB permission is requested automatically when PalmUSBManager detects the device",
      "callback": "onCheckPermission(0) means granted — then call initDevice(context)",
      "removal": "onUSBRemoved() fires when device is unplugged — call terminateDevice()",
      "device_filter": "USB device filter: vendor-id=31109, product-id=4097"
    },
    "image_capture": {
      "loop_pattern": "DeviceTookImage thread calls captureImage() in a continuous loop",
      "success": "RETURN_DEVICE_SUCCESS means palm detected — check proximity_intensity before using",
      "no_trigger": "RETURN_DEVICE_ERROR_DEVICE_NOT_TRIGGERED means no palm detected — keep looping",
      "proximity_check": "If proximity_intensity > 500, palm is too close — warn user",
      "camera_dimensions": "Raw image is 400×640 pixels",
      "frame_queue": "Captured frames go into a bounded queue for the detect thread"
    },
    "roi_detection": {
      "method": "detectRoi(rawImage) extracts the palm vein region of interest",
      "success": "RETURN_SERVICE_SUCCESS means valid ROI — imageRoi can be used for enroll/identify",
      "failure": "Non-success means palm not properly positioned — discard frame and retry",
      "mask_display": "maskImage(rawImage) blurs the image for privacy display to the user"
    },
    "enrollment": {
      "listener": "setEnrollListener(PalmEnroll) must be called before starting enrollment",
      "flow": "captureImage loop → detectRoi → enroll(EnrollPicture(roiImage, rawImage)) — repeat until enrollComplete",
      "feature_count": "SDPVServiceConstant.FEATURE_NUM captures required (typically 5-6 valid frames)",
      "callbacks": {
        "complete": "enrollComplete(EnrollResult) — template in result.getTmpl()",
        "fail": "enrollFail(UnifiedMsg) — enrollment failed, user should retry",
        "progress": "enrollTimes(count) — number of successful captures so far"
      },
      "insert_after": "After enrollComplete, call insertPalm(template) to add to cache pool",
      "token_returned": "insertPalm returns a token string for cache management",
      "custom_token": "insertPalm(template, userId) allows custom token for user-linked identification"
    },
    "identification": {
      "method": "identifyFeature(roiImage, callback) compares against the internal cache pool",
      "callback": "Callback receives (code, message, palmToken) — code == RETURN_SERVICE_SUCCESS means match found",
      "token_resolution": "palmToken from callback maps to the token used in insertPalm — resolves to user_id",
      "led_feedback": "Green LED on success (lightLed(LED_GREEN, 1000)), red on failure (lightLed(LED_RED, 1000))"
    },
    "cache_management": {
      "insert": "insertPalm(template) or insertPalm(template, token) adds to cache pool",
      "remove": "removePalm(token) removes a specific template",
      "clear": "clearCachePool() removes all templates",
      "count": "palmsCount returns the number of templates in the cache",
      "persistence": "Cache pool data stored under /sdcard/SD_TEMPLATE/HANDS_TEMPLATE/"
    },
    "led_control": {
      "method": "lightLed(LEDColor, durationMs) — LEDColor.LED_RED, LED_GREEN, LED_BLUE",
      "duration": "Duration in milliseconds (e.g., 1000 for 1 second flash)"
    },
    "palm_positioning": {
      "distance": "Hand must be approximately 15-30cm from the scanner",
      "proximity_warning": "proximity_intensity > 500 means too close — display warning to user",
      "centered": "Hand must be centered on the scanner",
      "fingers_spread": "Fingers should be spread naturally"
    },
    "security": {
      "biometric_data_sensitive": "Templates, ROI images, and raw images are biometric PII — encrypt at rest",
      "template_never_transmitted": "Templates stay on-device — only tokens transmitted to backend",
      "license_required": "Valid license.dat required for initService()"
    },
    "storage_paths": {
      "root": "/sdcard/SD_TEMPLATE/",
      "license": "/sdcard/SD_TEMPLATE/LICENSE/license.dat",
      "templates": "/sdcard/SD_TEMPLATE/HANDS_TEMPLATE/",
      "images": "/sdcard/SD_TEMPLATE/PALM_IMG/"
    }
  },
  "outcomes": {
    "cache_pool_initialized": {
      "priority": 1,
      "given": [
        "App has started",
        "Cache pool has not been initialized yet"
      ],
      "then": [
        {
          "action": "call_service",
          "target": "SDPVUnifiedAPI.initCachePool",
          "description": "Initialize cache pool with context and ChipType.RK3568"
        },
        {
          "action": "emit_event",
          "event": "palm.cache_pool.initialized",
          "payload": [
            "cache_pool_count"
          ]
        }
      ],
      "result": "Cache pool initialized — template storage ready"
    },
    "service_initialized": {
      "priority": 2,
      "given": [
        "Cache pool is initialized",
        "License file exists at configured path"
      ],
      "then": [
        {
          "action": "call_service",
          "target": "SDPVUnifiedAPI.initService",
          "description": "Initialize service with license file path"
        },
        {
          "action": "call_service",
          "target": "SDPVUnifiedAPI.initModel",
          "description": "Load the deep learning model for palm vein recognition"
        },
        {
          "action": "emit_event",
          "event": "palm.service.initialized",
          "payload": []
        }
      ],
      "result": "Algorithm service and model loaded — ready for USB device"
    },
    "service_init_failed": {
      "priority": 3,
      "error": "PALM_INVALID_LICENSE",
      "given": [
        "License file is missing or invalid"
      ],
      "then": [
        {
          "action": "emit_event",
          "event": "palm.sdk.init_failed",
          "payload": [
            "error_code"
          ]
        }
      ],
      "result": "Service initialization fails — check license.dat"
    },
    "usb_permission_granted": {
      "priority": 4,
      "given": [
        "Service is initialized",
        "PalmUSBManager detects USB device (vendor 31109, product 4097)"
      ],
      "then": [
        {
          "action": "call_service",
          "target": "PalmUSBManager.initUSBPermission",
          "description": "Request USB permission from Android system"
        },
        {
          "action": "emit_event",
          "event": "palm.usb.permission_granted",
          "payload": []
        }
      ],
      "result": "USB permission granted — ready to open device"
    },
    "device_opened": {
      "priority": 5,
      "given": [
        "USB permission has been granted"
      ],
      "then": [
        {
          "action": "call_service",
          "target": "SDPVUnifiedAPI.initDevice",
          "description": "Open USB connection to palm scanner"
        },
        {
          "action": "set_field",
          "target": "firmware_version",
          "value": "returned by api.firmwareVersion"
        },
        {
          "action": "set_field",
          "target": "serial_number",
          "value": "returned by api.serialNumber"
        },
        {
          "action": "emit_event",
          "event": "palm.device.opened",
          "payload": [
            "firmware_version",
            "serial_number"
          ]
        }
      ],
      "result": "Device connected — firmware and serial obtained"
    },
    "device_not_connected": {
      "priority": 6,
      "error": "PALM_DEVICE_NOT_CONNECTED",
      "given": [
        "Service is initialized",
        "initDevice returns non-success code"
      ],
      "then": [
        {
          "action": "emit_event",
          "event": "palm.device.disconnected",
          "payload": [
            "error_code"
          ]
        }
      ],
      "result": "Device not connected — check USB cable and retry"
    },
    "image_captured": {
      "priority": 7,
      "given": [
        "Device is open and idle",
        "captureImage returns RETURN_DEVICE_SUCCESS",
        {
          "field": "proximity_intensity_max",
          "source": "system",
          "operator": "gte",
          "value": 0,
          "description": "Proximity intensity is within safe range"
        }
      ],
      "then": [
        {
          "action": "call_service",
          "target": "SDPVUnifiedAPI.detectRoi",
          "description": "Detect palm ROI in the captured image"
        },
        {
          "action": "set_field",
          "target": "palm_roi_image",
          "value": "ROI from detectRoi result"
        },
        {
          "action": "emit_event",
          "event": "palm.image.captured",
          "payload": [
            "proximity_intensity"
          ]
        }
      ],
      "result": "Image captured and ROI detected — ready for enrollment or identification"
    },
    "palm_too_close": {
      "priority": 8,
      "error": "PALM_HAND_POSITION_ERROR",
      "given": [
        "captureImage returns success",
        "proximity_intensity exceeds threshold (500)"
      ],
      "then": [
        {
          "action": "emit_event",
          "event": "palm.position.too_close",
          "payload": [
            "proximity_intensity"
          ]
        }
      ],
      "result": "Palm is too close to scanner — user should move hand back"
    },
    "template_enrolled": {
      "priority": 9,
      "given": [
        "Device is open and idle",
        "PalmEnroll listener is set via setEnrollListener()",
        "captureImage → detectRoi → enroll loop is running"
      ],
      "then": [
        {
          "action": "call_service",
          "target": "SDPVUnifiedAPI.enroll",
          "description": "Feed ROI image + raw image as EnrollPicture into enrollment pipeline"
        },
        {
          "action": "call_service",
          "target": "SDPVUnifiedAPI.insertPalm",
          "description": "Insert completed template into cache pool with user token"
        },
        {
          "action": "set_field",
          "target": "palm_template",
          "value": "template from EnrollResult.getTmpl()"
        },
        {
          "action": "set_field",
          "target": "palm_token",
          "value": "token returned by insertPalm()"
        },
        {
          "action": "create_record",
          "type": "palm_template",
          "target": "palm_templates",
          "description": "Store template in local database linked to user account"
        },
        {
          "action": "emit_event",
          "event": "palm.template.registered",
          "payload": [
            "user_id",
            "palm_token"
          ]
        }
      ],
      "result": "Palm enrolled, template in cache pool and database — user can now identify by palm",
      "transaction": true
    },
    "enrollment_failed": {
      "priority": 10,
      "error": "PALM_REGISTRATION_FAILED",
      "given": [
        "Enrollment is in progress",
        {
          "any": [
            "enrollFail callback fires",
            "Capture loop times out before enough valid frames",
            "User moves hand during capture"
          ]
        }
      ],
      "then": [
        {
          "action": "emit_event",
          "event": "palm.template.registration_failed",
          "payload": [
            "error_code",
            "stage"
          ]
        }
      ],
      "result": "Enrollment failed — user should keep hand steady and try again"
    },
    "palm_identified": {
      "priority": 11,
      "given": [
        "Device is open and idle",
        "ROI image available from detectRoi()",
        "Cache pool has at least one template"
      ],
      "then": [
        {
          "action": "call_service",
          "target": "SDPVUnifiedAPI.identifyFeature",
          "description": "Compare ROI image against cache pool — callback returns (code, msg, palmToken)"
        },
        {
          "action": "call_service",
          "target": "SDPVUnifiedAPI.lightLed",
          "description": "Flash green LED on successful identification"
        },
        {
          "action": "emit_event",
          "event": "palm.match.succeeded",
          "payload": [
            "palm_token",
            "user_id"
          ]
        }
      ],
      "result": "Palm identified — token resolves to registered user"
    },
    "identification_failed": {
      "priority": 12,
      "error": "PALM_VERIFICATION_FAILED",
      "given": [
        "identifyFeature callback returns non-success code"
      ],
      "then": [
        {
          "action": "call_service",
          "target": "SDPVUnifiedAPI.lightLed",
          "description": "Flash red LED on failed identification"
        },
        {
          "action": "emit_event",
          "event": "palm.match.failed",
          "payload": [
            "error_code"
          ]
        }
      ],
      "result": "Palm does not match any registered template"
    },
    "usb_device_removed": {
      "priority": 13,
      "given": [
        "Device is open",
        "PalmUSBManager fires onUSBRemoved()"
      ],
      "then": [
        {
          "action": "call_service",
          "target": "SDPVUnifiedAPI.terminateDevice",
          "description": "Clean up device connection"
        },
        {
          "action": "emit_event",
          "event": "palm.device.disconnected",
          "payload": [
            "error_code"
          ]
        }
      ],
      "result": "USB device removed — scanner unavailable until reconnected"
    },
    "operation_timed_out": {
      "priority": 14,
      "error": "PALM_TIMEOUT",
      "given": [
        {
          "any": [
            "Capture loop is running",
            "Enrollment is in progress",
            "Identification is in progress"
          ]
        },
        {
          "field": "elapsed_time",
          "source": "system",
          "operator": "gt",
          "value": "timeout_seconds",
          "description": "Operation exceeds configured timeout"
        }
      ],
      "then": [
        {
          "action": "emit_event",
          "event": "palm.operation.timeout",
          "payload": [
            "operation_type",
            "timeout_seconds"
          ]
        }
      ],
      "result": "Operation timed out — user should try again"
    }
  },
  "errors": [
    {
      "code": "PALM_INVALID_LICENSE",
      "status": 403,
      "message": "Palm vein scanner license is invalid or missing"
    },
    {
      "code": "PALM_DEVICE_NOT_CONNECTED",
      "status": 500,
      "message": "Palm vein scanner is not connected — check USB cable",
      "retry": true
    },
    {
      "code": "PALM_USB_PERMISSION_DENIED",
      "status": 403,
      "message": "USB permission denied for palm vein scanner"
    },
    {
      "code": "PALM_DEVICE_BUSY",
      "status": 409,
      "message": "Scanner is busy with another operation",
      "retry": true
    },
    {
      "code": "PALM_EXTRACTION_FAILED",
      "status": 422,
      "message": "Could not detect palm vein ROI — reposition your hand",
      "retry": true
    },
    {
      "code": "PALM_REGISTRATION_FAILED",
      "status": 422,
      "message": "Palm enrollment failed — keep your hand steady and try again",
      "retry": true
    },
    {
      "code": "PALM_VERIFICATION_FAILED",
      "status": 401,
      "message": "Palm does not match any registered pattern"
    },
    {
      "code": "PALM_TIMEOUT",
      "status": 422,
      "message": "Operation timed out — please try again",
      "retry": true
    },
    {
      "code": "PALM_HAND_POSITION_ERROR",
      "status": 422,
      "message": "Palm is too close — hold your hand 15-30cm from the scanner",
      "retry": true
    },
    {
      "code": "PALM_COMMUNICATION_FAILED",
      "status": 500,
      "message": "USB communication with scanner failed",
      "retry": true
    },
    {
      "code": "PALM_CACHE_INSERT_FAILED",
      "status": 500,
      "message": "Failed to insert template into cache pool"
    },
    {
      "code": "PALM_PARAMETER_ERROR",
      "status": 400,
      "message": "Invalid parameter passed to scanner operation"
    },
    {
      "code": "PALM_INSUFFICIENT_MEMORY",
      "status": 500,
      "message": "Insufficient memory for scanner operation"
    }
  ],
  "events": [
    {
      "name": "palm.cache_pool.initialized",
      "payload": [
        "cache_pool_count"
      ],
      "description": "Cache pool initialized with template count"
    },
    {
      "name": "palm.service.initialized",
      "payload": [],
      "description": "Algorithm service and model loaded"
    },
    {
      "name": "palm.sdk.init_failed",
      "payload": [
        "error_code"
      ],
      "description": "SDK initialization failed"
    },
    {
      "name": "palm.usb.permission_granted",
      "payload": [],
      "description": "USB permission granted for palm scanner"
    },
    {
      "name": "palm.device.opened",
      "payload": [
        "firmware_version",
        "serial_number"
      ],
      "description": "Scanner device opened via USB"
    },
    {
      "name": "palm.device.disconnected",
      "payload": [
        "error_code"
      ],
      "description": "Scanner device disconnected or USB removed"
    },
    {
      "name": "palm.image.captured",
      "payload": [
        "proximity_intensity"
      ],
      "description": "Image captured and ROI detected"
    },
    {
      "name": "palm.position.too_close",
      "payload": [
        "proximity_intensity"
      ],
      "description": "Palm is too close to scanner"
    },
    {
      "name": "palm.template.registered",
      "payload": [
        "user_id",
        "palm_token"
      ],
      "description": "Palm enrolled — template in cache pool and database"
    },
    {
      "name": "palm.template.registration_failed",
      "payload": [
        "error_code",
        "stage"
      ],
      "description": "Enrollment failed"
    },
    {
      "name": "palm.match.succeeded",
      "payload": [
        "palm_token",
        "user_id"
      ],
      "description": "Palm identified against cache pool"
    },
    {
      "name": "palm.match.failed",
      "payload": [
        "error_code"
      ],
      "description": "Palm did not match any cached template"
    },
    {
      "name": "palm.operation.timeout",
      "payload": [
        "operation_type",
        "timeout_seconds"
      ],
      "description": "Operation exceeded timeout"
    },
    {
      "name": "palm.operation.cancelled",
      "payload": [],
      "description": "Capture/enroll/identify operation cancelled"
    }
  ],
  "related": [
    {
      "feature": "biometric-auth",
      "type": "recommended",
      "reason": "Biometric-auth uses the palm vein SDK to provide alternative authentication"
    },
    {
      "feature": "palm-pay",
      "type": "optional",
      "reason": "Palm pay links palm vein templates to payment proxies for hands-free payment"
    },
    {
      "feature": "terminal-enrollment",
      "type": "optional",
      "reason": "At-terminal enrollment uses palm vein SDK for walk-up registration"
    }
  ],
  "agi": {
    "goals": [
      {
        "id": "reliable_palm_vein",
        "description": "USB palm vein scanner integration using SDPVUnifiedAPI — capture, ROI detection, enrollment, 1:N identification, and cache pool management",
        "success_metrics": [
          {
            "metric": "success_rate",
            "target": ">= 99.5%",
            "measurement": "Successful operations divided by total attempts"
          },
          {
            "metric": "error_recovery_rate",
            "target": ">= 95%",
            "measurement": "Errors that auto-recover without manual intervention"
          }
        ],
        "constraints": [
          {
            "type": "availability",
            "description": "Must degrade gracefully when USB device is unavailable",
            "negotiable": false
          },
          {
            "type": "security",
            "description": "Biometric templates must be encrypted at rest and never logged in plaintext",
            "negotiable": false
          }
        ]
      }
    ],
    "autonomy": {
      "level": "supervised",
      "escalation_triggers": [
        "error_rate > 5"
      ]
    },
    "safety": {
      "action_permissions": [
        {
          "action": "cache_pool_initialized",
          "permission": "autonomous"
        },
        {
          "action": "service_initialized",
          "permission": "autonomous"
        },
        {
          "action": "service_init_failed",
          "permission": "autonomous"
        },
        {
          "action": "usb_permission_granted",
          "permission": "autonomous"
        },
        {
          "action": "device_opened",
          "permission": "autonomous"
        },
        {
          "action": "device_not_connected",
          "permission": "autonomous"
        },
        {
          "action": "image_captured",
          "permission": "autonomous"
        },
        {
          "action": "palm_too_close",
          "permission": "autonomous"
        },
        {
          "action": "template_enrolled",
          "permission": "autonomous"
        },
        {
          "action": "enrollment_failed",
          "permission": "autonomous"
        },
        {
          "action": "palm_identified",
          "permission": "autonomous"
        },
        {
          "action": "identification_failed",
          "permission": "autonomous"
        },
        {
          "action": "usb_device_removed",
          "permission": "autonomous"
        },
        {
          "action": "operation_timed_out",
          "permission": "autonomous"
        }
      ]
    },
    "tradeoffs": [
      {
        "prefer": "reliability",
        "over": "throughput",
        "reason": "biometric failures erode user trust and block payment"
      }
    ],
    "verification": {
      "invariants": [
        "biometric templates are never logged in plaintext",
        "all data access is authenticated and authorized",
        "error messages never expose internal system details",
        "state transitions follow the defined state machine — no illegal transitions",
        "USB permission is always checked before device operations"
      ]
    }
  },
  "extensions": {
    "sdk": {
      "library": "SDPalmVeinUsb",
      "version": "2.3.2.311",
      "release_date": "2024-12-23",
      "artifact": "SDPalmVeinUsb-2.3.2.311-2024-12-23.aar",
      "vendor": "SaintDeem",
      "platform": "android",
      "api_class": "com.saintdeem.palmvein.SDPVUnifiedAPI",
      "usb_manager": "com.saintdeem.palmvein.usb.utils.PalmUSBManager",
      "key_classes": [
        "SDPVUnifiedAPI — singleton SDK entry point",
        "PalmUSBManager — USB permission and connection lifecycle",
        "PalmUSBManagerListener — callbacks for USB permission/attach/detach",
        "PalmEnroll — enrollment callback interface (enrollComplete, enrollFail, enrollTimes)",
        "EnrollResult — enrollment output containing template bytes",
        "EnrollPicture — input to enroll() containing ROI image and raw image",
        "CaptureResult — output of captureImage() with image bytes and proximity_intensity",
        "DetectRoiResult — output of detectRoi() with imageRoi bytes",
        "LEDColor — enum for LED control (LED_RED, LED_GREEN, LED_BLUE)",
        "SDPVDeviceConstant — device result codes",
        "SDPVServiceConstant — service result codes and FEATURE_NUM"
      ],
      "camera": {
        "width": 400,
        "height": 640
      }
    }
  }
}