Integrity and reproducibility¶
okr attests one byte set: the pruned source tree that an agent reads. The
lock records exactly three kinds of digest, each with a consumer.
| Digest | Covers | Used by |
|---|---|---|
config-digest |
The normalized okr.toml model |
sync and status, to detect a stale lock |
environment-digest |
The lock format version and every sorted entry | Evaluation harnesses and verify |
tree-digest |
The files of one vendored entry after pruning | verify, and sync when it rebuilds a locked source |
Digest naming and values¶
Every serialized attestation field uses a <subject>-digest name and an
algorithm-tagged value such as sha256:<lowercase-hex>. The subject says which
bytes are covered; the value says how they were digested. TOML keys use kebab
case; their JSON manifest equivalents use snake_case (environment_digest and
tree_digest).
The sha256 configuration key for a direct URL is intentionally
algorithm-specific because it is an input pin. A git commit is a
source control identifier rather than an okr content digest.
Why there is no artifact digest¶
Earlier lock formats also recorded an artifact-digest for the downloaded
archive or normalized clone archive. It was dropped because it had no job the
other fields do not already do.
Every source has an upstream identity without it: a CRAN package is fixed by
its snapshot, version, and URL; a Git source by its full commit; and a direct
url source by the sha256 pin declared in okr.toml, which is still checked
before the download is committed to the cache. The tree digest catches every
change to the bytes agents read. An archive digest, by contrast, pins bytes
that forges do not promise to keep stable, so it could fail while every
vendored file was identical.
Offline replay does not need it either. The cache keys each artifact by its
fetch method and source, and the locked fetch-method selects which key to
replay.
This is the same choice Go makes in go.sum, which hashes a module's file
tree rather than its zip, and the choice uv and Cargo make by recording no
hash for Git sources.
Tree digest¶
For each vendored entry, okr recursively inventories regular files. Paths are
UTF-8, normalized to /, and sorted. Every record has this form:
The records are joined with LF and no trailing newline, then hashed again with
SHA-256. The resulting tree-digest covers all source bytes exposed to the agent.
Archive and clone symbolic links are normalized to regular files containing
their link-target bytes before the inventory is built. Symlinks introduced
later and other non-files are rejected rather than hashed.
Only the aggregate digest is serialized. Per-file hashes are internal inputs, so lockfiles and manifests remain compact as source trees grow. A verification failure therefore identifies the changed entry and aggregate digest rather than claiming a stored per-file diagnosis.
When sync rebuilds an entry that a fresh lock from the same okr release
already records, the rebuilt tree must reproduce the locked tree digest. A
difference means the locked source changed upstream and is reported as a fetch
error naming the entry. If the change is intended, delete okr.lock and sync
again to lock the new content.
Environment digest¶
The environment digest hashes the lock format version and the sorted package
and reference records, including every aggregate tree digest. It is the compact
stamp an evaluation harness can record for a run. It is derived from the rest
of the lock, but it is recorded so a harness can read it without running okr
and so verify can detect a manually edited lock.
Other deterministic lock properties include:
- packages and references sorted by name within their kind;
generatedset to snapshot midnight, or the Unix epoch for a remote-only lock, rather than wall-clock time;- a normalized configuration digest for staleness detection; and
- stable TOML serialization.
Comments and presentation changes in okr.toml do not affect its normalized
configuration digest. Behavioral configuration changes do.
Why fetch method is provenance¶
A forge-generated archive may honor .gitattributes rules such as
export-ignore and export-subst; a Git checkout of the same commit may not
produce identical files. The lock therefore records one of tarball,
forge-tarball, gh, or git-clone.
Reproduction replays the cached artifact corresponding to the locked method. It does not silently switch methods and assume the same commit implies the same tree.
Lock format version¶
okr.lock starts with a version key. Each okr release reads and writes one
lock format version. When sync finds a lock written in an older format, it
regenerates the lock and says so; status and verify report the older
version and ask for okr sync. The current version is 2. Version 1 also
recorded an artifact digest per entry.
What verify checks¶
okr verify recomputes the complete vendor state and checks:
- lock format version and sorted entry order;
- the lock's environment digest;
- every package and reference tree digest;
- missing or unexpected vendor entries; and
- the exact generated Markdown and JSON manifests.
Any tree or generated-file drift exits with code 4, independent of strictness.
status reports whether the current normalized configuration digest still
matches the lock, and sync creates a new lock whenever it does not.
--strict adds a read-only comparison between locked package versions and the
installed R library. R absence is a successful skip; a mismatch when inspection
succeeds is a strict failure.