One lock test shuffled the same three inputs 100 times. The canonical lock bytes stayed the same.
A separate golden fixture contained two duplicate pairs, one byte-identical and one LF/CRLF-equivalent. One test built it once in each of two workspace paths. Another built it twice from one path.
Those fixture builds produced byte-identical portable artifacts with the same bundle digest:
3096d7b4492124df4e893d27b15a9d59ffe8a9fad80d24cd8265262c2251866a
Then one relevant line changed. Its source identity changed, its chunk identity changed, and the final bundle digest changed. When the finished artifact was tampered with, the offline verifier failed closed.
That is the shipped result in Distill Lock v0.
Different discovery. One compiled context.
PORTABLE BUILD / LOCK V0100 shuffled enumerations2 workspace copies1 workspace copy3096d7b4…2251866a - sources
- 7
- duplicates
- 2
- selected
- 3
- tokens
- 34
Evidence boundary: this is deterministic context compilation, not deterministic model output. The proof uses a synthetic fixture and supported macOS/Linux portability checks. It does not establish production quality or adoption. Token budgeting pins an algorithm identity, not semantic optimality. Deduplication is exact only.
Probabilistic models need deterministic envelopes. Context is a compiled input artifact, not an incidental runtime prompt.
Runtime prompt assembly hides variance
A prompt can look stable while its inputs move.
Filesystem enumeration does not promise a useful order. Two paths can hold the same bytes. The same text can arrive with LF, CRLF, or CR line endings. A relative path can resolve from a different working directory. A process can also leak timestamps, absolute paths, usernames, hostnames, permissions, or environment values into an artifact without making that change visible to the caller.
If assembly happens inside the request path, those choices become part of model input without becoming part of the review surface. A prompt string may be logged after the fact, but the system still cannot answer which source set, normalization rules, duplicate policy, budget rule, or tool identity produced it.
Distill Lock moves those choices before inference. The output becomes something a reviewer can inspect, hash, store, compare, and verify.
Lock freezes the input contract
distill lock reads a canonical JSON configuration. The configuration names the source root, the explicit source allowlist, configured exclusions, chunk size, token budget, and metadata-removal policy.
The command inventories every file under the source root. Listed files become candidates. Configured exclusions stay visible with the reason configured_exclusion. Unlisted files stay visible with the reason not_listed. A new file is observable drift, not an implicit prompt addition.
Each inventory entry records two identities:
- the original byte length and SHA-256;
- the normalized byte length and SHA-256.
Normalization decodes valid UTF-8, applies Unicode NFC, replaces CRLF and CR with LF, and stops there. It preserves leading, trailing, and interior whitespace. It also preserves whether the file ends with a newline.
That split matters. LF and CRLF originals keep different original-byte hashes, while their normalized content can share one identity and deduplicate exactly.
The lock also freezes tool, runtime, and algorithm identities. Canonicalization, chunking, token estimation, exact deduplication, selection, bundle rendering, and canonical JSON each have a versioned name. If an implementation change can alter portable bytes, the identity or schema must change with it.
Build refuses drift
distill build does not discover a fresh best effort. It re-reads the locked world.
Missing, changed, new, unsupported, duplicated, or unsafe inputs stop the build. The command never relocks on the caller's behalf. The configuration, source inventory, tool identity, supported Go runtime, and every algorithm identity still have to match.
Only then does it render the selected chunks in stable order and write a fresh output directory:
context-output/
├── context.bundle.md
├── context.lock.json
├── context.manifest.json
└── SHA256SUMS
The portable files contain no timestamp, absolute path, working directory, hostname, username, inode, permission bits, or environment variable. The build performs no model, provider, retrieval, or network call.
The output is staged beside its destination. Files are synchronized before publication. Linux uses renameat2(RENAME_NOREPLACE) and macOS uses renamex_np(RENAME_EXCL). The destination must not exist, so a failed build cannot replace a valid artifact.
Publication is the commit point. A rare parent-directory sync failure returns a distinct published but durability unconfirmed error with the committed path. The caller must verify that path instead of retrying and risking a misleading outcome.
Trust is narrow. Failure is explicit.
THREE STEPS / NO HIDDEN STATEFreeze the exact inputs.
Pin the files, configuration, tool, runtime, algorithms, and selection rules.
Compile one canonical artifact.
Refuse drift, render stable bytes, hash every portable file, and publish once.
Check it anywhere, offline.
Read only the artifact and reject missing, extra, changed, or unsafe files.
Verify checks the artifact offline
distill verify reads only the output directory. It does not need the source tree.
The verifier checks:
- the exact four-file allowlist;
- regular-file and no-symlink rules;
- schema, tool, runtime, and algorithm identities;
- canonical JSON bytes;
- every recorded byte length and SHA-256;
- configuration, lock, source, chunk, manifest, and bundle relationships;
- exact bundle regeneration; and
SHA256SUMS.
An unexpected file fails. A missing file fails. A changed length, hash, relationship, or regenerated bundle fails.
Standalone verification proves that the artifact is internally consistent. It does not prove who supplied it. When another principal may replace the directory, --expected-lock-sha256 adds a trusted lock digest obtained through a separate channel. That digest anchors the selected chunk identities and regenerated bundle.
Exact dedup and a pinned budget
Distill Lock v0 does not use semantic similarity.
Normalized files are split into valid UTF-8 chunks. Candidates are ordered by path, start byte, end byte, then digest. For the same chunk digest and byte length, the first candidate becomes the representative. Later exact matches receive an exact_duplicate_of reason.
The token estimate is fixed as ceil(normalized UTF-8 bytes / 4). Unique chunks enter the bundle in order while each complete chunk fits the remaining budget. Chunks are never cut to fill the last few tokens.
This makes order, IDs, and budget decisions reproducible. It does not make the estimate a tokenizer, rank passages by meaning, or choose the best context for a task.
One line changes the identities it should
The merged demo changes 42 to 43 in code/example.go.
The source SHA-256 changes from:
32f7f5d8a8a1b269bd8e1d2b05f26150d3841850bed7734f132e6745110066bb
to:
52098e1f1e5ea1daf0443be4f2711153f64d2bee27a8cd3e5cb3571831e9a71b
The file fits in one chunk, so the affected chunk changes across the same pair. The bundle digest changes from:
3096d7b4492124df4e893d27b15a9d59ffe8a9fad80d24cd8265262c2251866a
to:
212492878b13ff99ae32a367172cf9d9f06741b7750b961bb5ef88eb9adff019
A separate test mutates a configured excluded file. Its inventory identity changes, but the bundle does not. The system records the drift without pretending that excluded bytes entered the compiled context.
Change propagates only when it should.
MUTATION / INVALIDATION- source
32f7f5d8…110066bb→52098e1f…31e9a71b - chunk
32f7f5d8…110066bb→52098e1f…31e9a71b - bundle
3096d7b4…2251866a→21249287…9adff019
excluded bytes never enter selection unchangedThe filesystem is part of the trust boundary
Deterministic bytes are not useful if another principal can swap a checked path before publication.
Distill Lock rejects absolute paths, traversal, empty path segments, backslashes, control bytes, and duplicate canonical paths. It rejects every symlink, including links whose targets remain inside the source root. Devices, sockets, FIFOs, and unsupported file types fail instead of disappearing from the inventory.
The source, configuration, lockfile, output, and staging paths form a local trust boundary. Directory components are inspected without following links. Ancestors and source directories must be owned by the current user or root. Unsafe group or world write access fails unless a directory has the sticky bit. Source, configuration, and lock files cannot be group or world writable.
Linux checks POSIX ACL mutation rights after applying the ACL mask. macOS rejects write-capable permit entries because the CGO-free implementation cannot safely resolve arbitrary ACL principals. The same operating-system account remains trusted. This is not a same-account sandbox.
The exact merged head passed build, lint, Ubuntu portability, and macOS portability checks. Exact-head code, security, operations, determinism, and documentation reviews were clean.
Reproduce the proof
From the merged repository:
git clone https://github.com/Siddhant-K-code/distill.git
cd distill
git checkout a9b14667024c27c32c9c9dfb9c8dc35865990979
The README documents these commands:
distill lock path/to/config.json --output path/to/context.lock.json
distill build path/to/context.lock.json --output path/to/context-output
distill verify path/to/context-output
distill verify path/to/context-output --expected-lock-sha256 <trusted-digest>
# Synthetic fixture: build, offline verify, repeatability, and mutation proof.
make distill-lock-demo
The clean demo logged these result fields. Go adds the test location prefix:
sources=7 duplicates=2 selected=3 tokens=34 bundle=3096d7b4492124df4e893d27b15a9d59ffe8a9fad80d24cd8265262c2251866a lock=a4d44357e284f359c970af70aea744346703ecd37e8058bf7b8d3bd30f11842b manifest=7f6849eb7f22a1de00e851bfd42ccef32dea1d82751bca9d7c592286ec6929fd
repeat=byte-identical mutation_source=32f7f5d8a8a1b269bd8e1d2b05f26150d3841850bed7734f132e6745110066bb->52098e1f1e5ea1daf0443be4f2711153f64d2bee27a8cd3e5cb3571831e9a71b mutation_chunk=32f7f5d8a8a1b269bd8e1d2b05f26150d3841850bed7734f132e6745110066bb->52098e1f1e5ea1daf0443be4f2711153f64d2bee27a8cd3e5cb3571831e9a71b mutation_bundle=3096d7b4492124df4e893d27b15a9d59ffe8a9fad80d24cd8265262c2251866a->212492878b13ff99ae32a367172cf9d9f06741b7750b961bb5ef88eb9adff019 mutated_lock_bytes=5225
The four integrity anchors are:
| Artifact | SHA-256 |
|---|---|
context.bundle.md |
3096d7b4492124df4e893d27b15a9d59ffe8a9fad80d24cd8265262c2251866a |
context.lock.json |
a4d44357e284f359c970af70aea744346703ecd37e8058bf7b8d3bd30f11842b |
context.manifest.json |
7f6849eb7f22a1de00e851bfd42ccef32dea1d82751bca9d7c592286ec6929fd |
SHA256SUMS |
ad4d593b7ded2a53023a24e767351b21570839f0b4fea9348d8e600252b8b0ee |
The fixture contains seven inventoried sources, including two duplicate pairs, one byte-identical and one LF/CRLF-equivalent, plus one configured exclusion and one unlisted file. The two redundant chunks yield duplicates=2. Three chunks totaling 34 estimated tokens enter the bundle.
What this does not guarantee
Distill Lock v0 does not provide:
- semantic or near-duplicate matching;
- a vector database;
- model, provider, retrieval, agent, or network determinism;
- deterministic model output;
- decision replay;
- a model adapter or agent framework;
- production quality or adoption; or
- proof that the pinned token estimate is semantically optimal.
It guarantees a smaller boundary: the same locked sources, configuration, and supported tool and runtime identities produce byte-identical portable context artifacts and digests on supported macOS and Linux systems with Go 1.24 through Go 1.26.
That boundary is enough to move context from invisible runtime state into a reviewable build artifact.
Evidence: Distill repository · Merged PR #97 · Merged commit a9b1466 · Frozen Distill Lock v0 specification