# Sidecar consistency lab

An original, deliberately small Python 3.10+ teaching helper. It compares the bytes of an explicitly selected local asset with `output.sha256` in an explicitly selected generation record. It displays a few **recorded, unverified** labels. It does not certify legal clearance, authorship, record authenticity, valid glTF, rig quality, or complete processing lineage.

## Run

Download [check_sidecar.py](check_sidecar.py) and use your own explicitly chosen paths:

```sh
python3 check_sidecar.py /path/to/asset.glb /path/to/record.json
```

No dependencies to install. No network requests, archive extraction, imported model code, or subprocess execution. Paths and URLs inside JSON are never opened. JSON is treated as data. The helper does not search for sidecars or open the source image.

Output is JSON on standard output. Exit status is 0 only for `hash_match`; all other report statuses exit 1. Argument-usage errors use argparse's exit 2. A match is only agreement between the supplied file and the supplied hash. Someone able to replace both can create a matching pair.

## Reproduce the demonstration

Download [check_sidecar.py](check_sidecar.py), [build_fixtures.py](build_fixtures.py), and [test_sidecar.py](test_sidecar.py) into one directory. Then:

```sh
python3 -m unittest -v test_sidecar.py
python3 build_fixtures.py my-synthetic-fixtures
python3 check_sidecar.py my-synthetic-fixtures/synthetic-original.glb my-synthetic-fixtures/synthetic-pipeline.provenance.json
python3 check_sidecar.py my-synthetic-fixtures/synthetic-renamed.glb my-synthetic-fixtures/synthetic-pixal3d.json
python3 check_sidecar.py my-synthetic-fixtures/synthetic-changed.glb my-synthetic-fixtures/synthetic-pixal3d.json
python3 check_sidecar.py my-synthetic-fixtures/synthetic-original.glb my-synthetic-fixtures/synthetic-finish.retopo-repaint.json
```

The builder refuses an existing destination directory. It creates tiny original text-byte fixtures with `.glb` filenames solely to exercise hashing. **They are not valid GLBs, AssetFurnace outputs, or evidence of product performance.** Their license labels are deliberately synthetic. The last two commands exit 1 by design.

[fixtures/actual-report.json](fixtures/actual-report.json) records an actual run of this helper on those synthetic fixtures:

| Case | Observed status |
|---|---|
| Original bytes + pipeline-shaped record | `hash_match` |
| Same bytes under a new name + Pixal3D-shaped record | `hash_match` |
| Edited bytes + unchanged record | `hash_mismatch` |
| Finish-shaped processing report | `unsupported_hash_evidence` |

The 19 unit tests passed on 2026-10-12. Tests cover the two supported field layouts, changes, ignored embedded paths, Finish report classification, missing hashes, malformed values, duplicate keys, nonfinite numbers, invalid encoding/deep JSON, file-size limits, missing/empty/nonregular inputs, symlinks, FIFO refusal, and fixture generation.

## Supported evidence

The reviewed AssetFurnace source revision is `7793fd3622bab7d3fd4526ba02433244d5bf02e2`. See [source-formats.csv](source-formats.csv) for pinned code links.

- Pipeline generation: schema version 1, backend at `model.backend`, digest at `output.sha256`; native filename convention `stem.provenance.json`.
- Pixal3D generation: schema version 1, top-level `backend` equal to `pixal3d`, digest at `output.sha256`; native filename convention `stem.json`.
- Finish processing: string `output` plus `stages` list is treated as Finish-shaped, unsupported hash evidence. The reviewed Finish record builder does not write the supported output digest. This is not a claim that the report is malformed or useless.

This is **not full schema validation**. Shape recognition is deliberately partial and cannot authenticate a producer. It does not inspect all fields, license conditions, components, source image hashes, software revisions, or prior stages. Unknown layouts are unsupported. Optional displayed labels may be null. A missing hash is not a mismatch. A changed final mesh legitimately differs from its raw generation file; preserve the raw record and document the transformation rather than replacing its hash to force a match.

## Input limits and boundaries

- Nonempty regular asset file, at most 256 MiB; nonempty UTF-8 JSON, at most 1 MiB.
- Final-component symlinks and nonregular files are refused. On platforms providing them, no-follow and nonblocking open flags add protection. This is not a hostile-filesystem sandbox; choose trusted local directories and do not edit inputs during a check.
- Reads are bounded; observed size/mtime changes during a read are refused. No lock or forensic immutability guarantee is provided.
- Duplicate JSON keys and nonfinite JSON numbers are rejected. Displayed string labels are limited to 4096 characters and JSON-escaped; URLs are never visited.
- No GLB header/geometry checks: the selected asset is hashed as opaque bytes, whatever its extension. Passing this helper says nothing about rendering or engine import.
- The output may echo selected paths in operating-system error messages and recorded labels in reports. Review reports before sharing them.

## Standards context

[SPDX file-checksum guidance](https://spdx.github.io/spdx-spec/v2.3.1/file-information/#84-file-checksum-field) explains content identity and the separate need to authenticate a checksum. This helper and its JSON are not SPDX documents. [Khronos glTF asset metadata](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-asset) makes copyright and generator metadata optional; a valid GLB alone is not a complete provenance record.

No AssetFurnace generation, rigging, animation, or export workflow was run for this lab.
