{
  "schemaVersion": 1,
  "slug": "truncated-video-reject-and-retry",
  "locale": "en",
  "evidenceClass": "executed-local-fixture-not-provider-generation",
  "providerModelTest": false,
  "productionTaskRun": false,
  "runCommand": "node run-recipe.mjs --out ./my-run",
  "environment": {
    "nodeVersion": "v24.18.0",
    "ffmpeg": {
      "version": "ffmpeg version 6.0 Copyright (c) 2000-2023 the FFmpeg developers",
      "binarySha256": "a90e3db6a3fd35f6074b013f948b1aa45b31c6375489d39e572bea3f18336584"
    }
  },
  "title": "Reject a truncated video and retry only the artifact download",
  "description": "Replay a local artifact endpoint that first returns half an MP4, then a complete file; require full decode, bound checksums and zero generation requests.",
  "goal": "Separate file-delivery recovery from generation recovery. A downloaded HTTP 200 can contain a broken video; retry the same known artifact identity in this offline fixture.",
  "preparation": [
    "Prepare Node.js and FFmpeg. The original two-second 320×180 source has twenty frames; its byte size and expected SHA-256 are captured in artifact-manifest.json.",
    "Inspect truncated.mp4: it is an actual byte-prefix truncation of the original source, not a fabricated error label. The first local download returns those broken bytes.",
    "Run in a fresh directory. All HTTP requests use one ephemeral 127.0.0.1 artifact URL, with a three-second timeout, a 2-MB response cap and finally cleanup. No production task is called."
  ],
  "steps": [
    {
      "title": "01. Keep the existing task and artifact identity",
      "body": "Read the manifest for local-paid-once and /artifact/local-paid-once. The fixture exposes an intentionally forbidden /generate route solely to count accidental resubmissions; the successful path never calls it. This local name does not represent an actual paid task."
    },
    {
      "title": "02. Reject the first complete HTTP response as broken media",
      "body": "The first response is HTTP 200 but contains 17,775 bytes, half of the 35,550-byte source. Run FFmpeg with -xerror and explode error detection through the entire video decode. The actual decoder failure is retained in the negative receipt."
    },
    {
      "title": "03. Retry the same artifact URL once",
      "body": "Fetch the exact same artifact identity a second time. The local server returns the complete original bytes. Check SHA-256 against the manifest, then fully decode twenty frames instead of accepting the network status alone."
    },
    {
      "title": "04. Audit the boundary between retries",
      "body": "Check exactly two downloads, zero generation requests and a closed local server. The bounded fixture has no backoff, signed-URL renewal or provider error classification; a real downloader must use its own service’s documented recovery rules."
    }
  ],
  "acceptance": [
    "The first HTTP 200 response contains the actual truncated byte prefix and differs from the expected SHA-256.",
    "Full FFmpeg decode of the truncated attempt exits with a recorded error.",
    "The second fetch uses the same artifact URL and the total download count is exactly two.",
    "The second file’s SHA-256 equals the complete artifact manifest.",
    "The recovered file fully decodes to twenty 320×180 frames.",
    "No /generate request occurs and the loopback server closes; all seven checks pass."
  ],
  "troubleshooting": [
    {
      "symptom": "HTTP status is 200 but the player cannot open the file.",
      "change": "Keep the downloaded bytes and run the full decoder; retry only the same artifact delivery after the media check fails."
    },
    {
      "symptom": "The second file is readable but has a different checksum.",
      "change": "Verify only the artifact-to-manifest binding before accepting it. Readability does not prove it is the expected task output."
    },
    {
      "symptom": "A file retry creates another generation request.",
      "change": "Change the retry target back to the saved artifact URL and rerun the zero-generation assertion. Keep generation submission outside this delivery recovery loop."
    }
  ],
  "ending": "The local replay proves recovery from this truncated MP4 while preserving artifact identity. It does not show a provider retry promise or a real payment, generation or signed-download execution.",
  "assetCaption": "Original tiny source video, actual truncated and recovered files, localhost replay and an artifact-retry diagram. No production or provider generation was run."
}
