> ## Documentation Index
>
> Fetch the complete documentation index at: https://lithi.ai/llms.txt
>
> Use this file to discover all available pages before exploring further.

---
title: Pin an input snapshot that cannot change
description: >-
  Pin an exact version, manifest or revision so your input cannot change under a
  running job. A moving prefix or a live table is not a frozen source.
route: /docs/compute/inputs/immutable-snapshots
page_id: docs-compute-inputs-immutable-snapshots
page_type: howTo
content_layer: guided documentation
surface_profile: guided_docs
audience: Integrators and AI agents binding a Compute job to an exact input
voice: D
reader_question: How do I give Compute a source that cannot change while the job runs?
primary_action: Pin your snapshot and request a quote
source_locale: en-US
source_status: APPROVED
source_version: docs-compute-inputs-immutable-snapshots-en-us-2026-09-05-v1
last_updated: '2026-09-05'
robots: index
claim_registry_pinned: true
claim_registry_resync: "npm run governed:tsx -- scripts/build-compute-public-claim-registry.ts --write"
---

## Before you start

You need a source that can name one exact version of itself. An object version id, a table snapshot or revision, or a manifest listing exact file versions all qualify.

You also need to have read the bytes at that version, so the digest you pin is the digest of what you inspected.

## What you pin

The `input` object carries a `snapshot` with two fields:

```json
"snapshot": {
  "version_id": "<the source's own version identifier>",
  "content_sha256": "<the digest of those exact bytes>"
}
```

`version_id` says which version your source calls it. `content_sha256` says what the bytes actually were. Together they mean the run cannot silently read something else.

## A raw file is not a snapshot

A raw file is bytes at a path. A snapshot is a promise that those bytes cannot be replaced under that name. The difference decides whether a result can ever be traced back to its input.

These are not frozen sources, however stable they look:

- A prefix or folder that new objects keep landing in. That is a query, not a version.
- A `latest` pointer, a moving tag or a branch name.
- A live table with no snapshot, revision or transaction id.
- Any location a second process can write to while your job runs.

If your source cannot name a version, make your own frozen copy first. Materialize the exact rows or files you want, [upload that copy as an artifact](/docs/compute/inputs/direct-upload), and pin its digest.

## When the input changes under you

Nothing is quietly reused. A quote is signed against the digest you pinned, carries an expiry, and is marked `non_authorizing = true` because it never starts work by itself.

If the input has moved by the time you seek admission, admission answers `REPLAN_REQUIRED`. That is the plan being remade, not a failure to explain away.

Recover in the same order every time. Re-read the descriptor at the version your quote names, then re-read the source at its current version. Rebuild the request with the new digest, quote again, and approve again. Approval is always its own act.

## Why the digest earns its place

Your result bundle carries `provenance.json`, which binds the input, workflow, model, schema and envelope digests together. That file is what lets you prove, months later, which bytes produced which row.

Without a pinned snapshot there is nothing on the input side to bind. The bundle is still complete, but the claim "this result came from that data" becomes an assertion rather than a check.

## What success looks like

Your quote echoes back the `version_id` and `content_sha256` you supplied. Read them before you approve.

After the run, `provenance.json` carries the same input digest, and `result-manifest.json` is written last as the only complete-bundle marker. Matching digests on both sides means the job read what you meant it to read.

## If you cannot freeze the source

Some sources genuinely cannot hold still, such as a table under continuous write. Do not pin a digest you cannot honor.

Instead, bound the read yourself: take one window, materialize it, pin that, and run it as its own job. That is the same pattern as [running a stream as bounded windows](/docs/compute/inputs/stream-windows).
