{
  "skills": ["vss-manage-video-io-storage"],
  "resources": {
    "platforms": {
      "L40S": {
        "gpu_count": 1
      }
    }
  },
  "env": "**No VSS profile is pre-deployed.** Either VIOS+NvStreamer must be running already, or the agent must stand them up via this skill's bundled [`references/deploy-vios-service.md`](../references/deploy-vios-service.md) runbook (the deploy is pre-authorized — see SKILL.md § Deployment prerequisite, *Pre-authorized autonomous mode*). NvStreamer (`vss-vios-nvstreamer`) comes up alongside VIOS in shipping profiles (`dev-profile-alerts`, `dev-profile-lvs`, `dev-profile-search`, all warehouse profiles); it does NOT need a separate deploy step. The agent must probe **both** `http://localhost:30888/vst/api/v1/sensor/version` (VIOS, expect `type: \"vst\"`) AND `http://localhost:${NVSTREAMER_HTTP_PORT:-31000}/vst/api/v1/sensor/version` (NvStreamer, expect `type: \"streamer\"`) before starting any check, and point the right curl calls at the right port — the two services share the same `/vst/api/v1/` prefix but live on different host ports. Do NOT invoke `/vss-deploy-profile`; the point of this eval is to exercise the standalone VIOS deploy (which brings NvStreamer up) + the NvStreamer API surface in `references/nvstreamer-api-reference.md` end-to-end. **Either deploy mode is acceptable** — direct-routing (`VST_NGINX_MODE=vst-direct`, like `dev-profile-base`) or SDRC-routed (the runbook's default). NvStreamer is CPU-only (no GPU reservation) so the L40S × 1 platform is over-provisioned for the streamer side; only the VIOS `streamprocessing-ms` container needs the GPU. Required env vars: `NGC_CLI_API_KEY` (for `nvcr.io/nvstaging/vss-core/*` image pulls including `vss-vios-nvstreamer`), `HOST_IP` (consumed by NvStreamer's `vstIp` field — must match the actual host IP or the emitted RTSP URLs will be unreachable from peer containers), `VSS_DATA_DIR` + `VSS_APPS_DIR` (host roots for NvStreamer's bind-mounted videos directory and config), `NVSTREAMER_IMAGE_TAG` (image tag pin — `2.1.0-26.05.2` is the shipping default), `NVSTREAMER_HTTP_PORT` (HTTP port, default `31000`), and `NVSTREAMER_INSTALL_ADDITIONAL_PACKAGES=true` (must be `true` or NvStreamer uploads fail with `Failed to get media information` — same libav-missing failure mode as VIOS, see `deploy-vios-service.md § Known Deployment Issues` Finding 9). The sample-video bundle `nvidia/vss-developer/dev-profile-sample-data:3.1.0` must be extracted to `/tmp/vss-sample-data/dev-profile-sample-data/` before any upload-style check runs (see `references/api-reference.md § Sample data bootstrap` for the ngc-cli command).",
  "expects": [
    {
      "query": "Upload the sample warehouse safety video (warehouse_safety_0001) to NvStreamer using the POST multipart API (single-chunk, with the nvstreamer-file-name header), then look up the RTSP URL NvStreamer auto-generated for the file.",
      "checks": [
        "The POST to http://localhost:${NVSTREAMER_HTTP_PORT:-31000}/vst/api/v1/storage/file with the `nvstreamer-file-name: warehouse_safety_0001.mp4` header and a single multipart `file` part (i.e. `-F 'file=@/tmp/vss-sample-data/dev-profile-sample-data/warehouse_safety_0001.mp4;type=video/mp4'`, with NO `nvstreamer-chunk-*` / `nvstreamer-identifier` headers) returns HTTP 2xx with a JSON body containing non-empty `id` and `streamId` fields.",
        "**POST-upload identifier semantics:** the response's `id` and `streamId` equal `warehouse_safety_0001` (filename-without-extension), NOT a UUID. The `sensorId` field is allowed to be returned as an empty string — read `id` / `streamId` instead, as documented in `references/nvstreamer-api-reference.md § 3 Method 3`.",
        "The response's `filePath` ends with `/home/vst/vst_release/streamer_videos/warehouse_safety_0001.mp4` and `filename` equals `warehouse_safety_0001`.",
        "After a short delay, `curl -sf http://localhost:${NVSTREAMER_HTTP_PORT:-31000}/vst/api/v1/sensor/list` returns a JSON array containing an entry whose `sensorId` equals `warehouse_safety_0001`, whose `name` equals `warehouse_safety_0001`, and whose `type` is `sensor_nvstream`.",
        "`curl -sf http://localhost:${NVSTREAMER_HTTP_PORT:-31000}/vst/api/v1/sensor/warehouse_safety_0001/streams` returns a non-empty JSON array whose first element has a `url` field starting with `rtsp://` and containing the path `/nvstream/`. The host portion of the URL is non-empty (not the broken `rtsp://:31554/...` form). The stream's `type` is `Rtsp` and `storageLocation` is `Local`. The returned stream entry does NOT contain a `vodUrl` field — NvStreamer never emits one (documented in `references/nvstreamer-api-reference.md § 2`).",
        "Posting the same file again with `nvstreamer-file-name: bad name.mp4` (containing a space) returns HTTP 400 `{\"error_code\": \"InvalidParameterError\", \"error_message\": \"Whitespaces not allowed in file name\"}` — confirms the filename-whitespace rule documented in `references/nvstreamer-api-reference.md § 3 Filename rule`. The previously uploaded `warehouse_safety_0001` sensor must still be intact after this rejected call."
      ]
    },
    {
      "query": "If the VIOS stream-processor is part of the active deployment, take the RTSP URL NvStreamer generated for the POST-uploaded warehouse_safety_0001 video and register it with VIOS as an upstream RTSP camera. Then verify VIOS records and serves it like any other camera. If VIOS is NOT part of the deployment (NvStreamer-only profile or stream-processor not running), skip the registration step and report that the handoff is not applicable — do NOT attempt `/sensor/add`.",
      "checks": [
        "**Precondition (run first):** probe `curl -sf --max-time 5 http://localhost:30888/vst/api/v1/sensor/version`. If the probe FAILS or the response's `type` is not `vst`, the VIOS stream-processor is not deployed; the agent must report 'VIOS stream-processor not present — handoff skipped' and the remaining checks in this trial are not applicable. If the probe SUCCEEDS with `type: \"vst\"`, proceed with the registration checks below.",
        "`curl -sf -X POST http://localhost:30888/vst/api/v1/sensor/add -H 'Content-Type: application/json' -d '{\"sensorUrl\": \"<RTSP URL from NvStreamer /sensor/warehouse_safety_0001/streams>\"}'` returns HTTP 2xx with a JSON body containing a non-empty `sensorId` field. **This sensorId is VIOS's own ID for the camera — different from the NvStreamer streamId.** Capture both for downstream steps.",
        "`curl -sf http://localhost:30888/vst/api/v1/sensor/list` returns a JSON array containing the VIOS-side sensorId, whose state is `online` and whose underlying URL (visible via `/sensor/<vios-sensor-id>/streams`) starts with `rtsp://`.",
        "After waiting ~10s for VIOS to start recording, `curl -sf http://localhost:30888/vst/api/v1/storage/<vios-stream-id>/timelines` returns a JSON array with at least one `{startTime, endTime}` element — confirms VIOS is recording the NvStreamer-served URL just like any real camera.",
        "`curl -sf http://localhost:30888/vst/api/v1/replay/stream/<vios-stream-id>/picture/url?startTime=<a-time-within-the-timeline-range>` with a `streamId: <vios-stream-id>` header returns a JSON object with a non-empty `imageUrl` field. `curl -sf -o /dev/null -w '%{http_code}' <imageUrl>` returns 200 with content-type starting with `image/` (use GET, not HEAD — VST lazy-renders snapshots).",
        "This validates the canonical NvStreamer → VIOS handoff: NvStreamer is the file → RTSP boundary, VIOS owns recording / playback / snapshot / clip-download downstream. See `references/nvstreamer-api-reference.md § Canonical workflow: NvStreamer → VIOS handoff`."
      ]
    },
    {
      "query": "Drop a second sample video file into NvStreamer's videos directory using docker cp (bypassing the upload API), then force a filesystem rescan so the new file shows up as a sensor and capture a snapshot of its first frame to verify it is playable.",
      "checks": [
        "Before scan: copying a sample file (e.g. `sample-warehouse-ladder.mp4`) into the running `vss-vios-nvstreamer` container's `/home/vst/vst_release/streamer_videos/` via `docker cp` and then immediately calling `/sensor/list` shows the file's basename is NOT yet present as a sensor (no entry whose `name` matches `sample-warehouse-ladder`).",
        "`curl -s -X POST http://localhost:${NVSTREAMER_HTTP_PORT:-31000}/vst/api/v1/sensor/scan -w '%{http_code}'` returns HTTP 200 with a body of `null` — the scan is async and does not include the new sensor list in its response.",
        "After a 2–5 second wait, `/sensor/list` includes a new entry whose `name` matches `sample-warehouse-ladder` (or the uniqueified form `sample-warehouse-ladder_<N>` if a name collision occurred) and whose `location` ends with `/home/vst/vst_release/streamer_videos/sample-warehouse-ladder.mp4`. The entry's `type` is `sensor_nvstream` and `state` is `online`.",
        "`curl -sf http://localhost:${NVSTREAMER_HTTP_PORT:-31000}/vst/api/v1/sensor/<new-sensorId>/streams` returns a usable RTSP URL of the form `rtsp://<host>:<lb-port>/nvstream/home/vst/vst_release/streamer_videos/sample-warehouse-ladder.mp4`. The `<lb-port>` lies within NvStreamer's configured RTSP server pool (default `31554`–`31561`).",
        "`curl -sf -o /tmp/nvs-frame.jpg -w '%{http_code} %{content_type}' http://localhost:${NVSTREAMER_HTTP_PORT:-31000}/vst/api/v1/live/stream/<new-sensorId>/picture?frameId=0` returns `200 image/jpeg`, the saved file is larger than 500 bytes, and `file /tmp/nvs-frame.jpg` reports `JPEG image data` (confirms the file is playable, not just present as a directory entry).",
        "Calling `/live/stream/<new-sensorId>/picture` without `frameId` returns HTTP 500 `{\"error_code\": \"VMSInternalError\", \"error_message\": \"Wrong time format or frameId provided\"}` — confirms the `frameId` requirement documented in `references/nvstreamer-api-reference.md § 5`."
      ]
    }
  ]
}
