# Preflight first and last frame files before binding a pair

Prove that the files assigned to first and last are the intended readable images with matching geometry. This does not prove semantic continuity or a provider’s first/last-frame capability.

This complete kit uses Node standard-library modules only. Install/use your own FFmpeg; the binary is not redistributed. Set FFMPEG_BIN to its absolute path if it is not on PATH.

Run from this directory and select a NEW output directory. Existing output directories are refused. On a memory-managed workstation, put its local serialization wrapper before the Node command. No provider credentials or off-device fixture requests are used.

```sh
node run-recipe.mjs --out ./my-run
```

Recorded execution: 7 checks passed, including 4 expected rejections. Node v24.18.0. ffmpeg version 6.0 Copyright (c) 2000-2023 the FFmpeg developers

Preparation

- Use Node.js and an installed FFmpeg. Set FFMPEG_BIN to its absolute path if it is not on PATH; the recorded fixture used FFmpeg 6.0.
- Inspect binding.json and the original 320×180 first.png/last.png practice images in executed/inputs. A separate 180×320 PNG deliberately violates the pair geometry.
- Keep the role name, basename and expected SHA-256 together. Run into a new output directory so an earlier manifest cannot silently refer to replaced input files.

## 01. Freeze each role-to-file assignment

The script creates a pair of small original RGB illustrations and writes a binding manifest. First and last have distinct bytes and edge markers. Hashes are captured from these exact files, not inferred from an upload order or a filename containing “first”.

## 02. Validate geometry and decode the image

For each bound file, read the PNG signature and IHDR width/height, then fully decode it through FFmpeg to one RGB frame. Both must be 320×180 and share the explicitly selected 16:9 canvas. Header dimensions alone would not establish that compressed pixels decode.

## 03. Replay the four unsafe assignments

Run the portrait-last, swapped filename, missing last and parent-path cases. Each is expected to fail for its own reason: dimension mismatch, hash binding, missing input or unsafe basename. failure-receipt.json retains the actual exception messages.

## 04. Retain the pair manifest with the files

Check seven passing assertions and the pre/post script hashes in execution-receipt.json. Keep both PNGs and binding.json together when adapting your own upload flow. Review camera, object and endpoint semantics separately; these file checks do not inspect them.

Acceptance

- The correct first/last pair produces one completely decoded RGB frame per PNG.
- Both roles report exactly 320×180 pixels and the cross-multiplication confirms 16:9.
- First and last have different SHA-256 values, matching their bound original files.
- The 180×320 replacement is rejected for dimension mismatch.
- Swapping the last filename into the first role without changing its expected hash is rejected.
- A missing file and a parent-path basename are rejected, with all seven checks recorded.

Troubleshooting

The tail image has the right subject but fails geometry. Change only its actual pixel canvas to the pair’s selected dimensions, re-export, and capture a new hash. Renaming a portrait file cannot change its geometry.

Two files have been uploaded in the wrong order. Correct the role-to-basename mapping, then regenerate both expected hashes from the selected files and recheck the manifest.

A header looks valid but decoding stops. Replace only the damaged file with a complete local copy and rerun full decode. Do not accept IHDR dimensions as proof of readable pixels.

Public receipt copies normalize only local checkout paths and include publicPathNormalization with the original receipt SHA-256; raw originals are retained outside public assets. The actual receipts are under executed/. failure-receipt.json lists expected negative cases, not failed-suite assertions. Original tiny fixtures are generated again into your new output directory; shipped executed inputs and outputs let you inspect the recorded run. Subprocesses have a 15-second timeout, an 8-MB output cap, argument arrays and no shell. Temporary raw buffers and local HTTP servers are cleaned in finally.

checksums.json records every distributed file except itself and the archive, avoiding self-reference. The ZIP includes that same checksum list. Recompute hashes after download; comparison requires trusting the expected list. execution-receipt.json records executed script hashes before and after the run. The complete kit archive is also bound by the publishing catalog asset inventory.

This pair manifest is a file-readiness record. It does not demonstrate a generated transition, endpoint compatibility or visual preservation between the two states.

Sources read on 2026-10-14

- Node.js filesystem API: local file inspection: https://nodejs.org/api/fs.html
- Node.js crypto API: SHA-256 hashing: https://nodejs.org/api/crypto.html
- FFmpeg command-line documentation: maps, codecs and error exit: https://ffmpeg.org/ffmpeg.html
