## [2026-06-12T11:56:37.658Z]
Implements the frozen input-hash contract (spec §3.1) that makes verdict reuse possible. Every (aspect, unit) verdict entry in the lock is keyed by a sha256 hash of a canonical object encoding the aspect id, scope, node path, rule source hash, subject files, verdict token, and kind discriminator ('llm' or 'deterministic'). LLM pairs additionally fold the aspect description, references, and tier config (excluding api_key and timeout). Deterministic pairs fold the observation set (read, list, exists, graph accesses beyond the subject files).

The core design constraint is that producers and verifiers — including across platforms and Yggdrasil versions — must compute byte-identical hashes. This requires code-point key ordering (the standard < comparator on strings) everywhere, never localeCompare which is environment-sensitive. This replaces the latent cross-platform bug in the old tier-identity.ts:25 which used localeCompare. Serialization and ingredient selection are pinned by golden tests in pair-hash-golden.json; changing them requires a deliberate breaking decision.
## [2026-06-12T12:09:59.164Z]
Fixed hashReadObservation to hash the raw bytes of the Buffer directly (sha256(bytes)) instead of converting through toString('binary') then UTF-8-encoding. The prior implementation sha256(utf8(latin1(bytes))) diverges from sha256(bytes) for any byte value >= 0x80 — multi-byte sequences are re-encoded during the string round-trip, producing a different digest than any independent producer hashing the same file bytes. Observation hashes must match what any independent verifier computes directly over file bytes so that read-observation replay is reproducible and tamper-evident.
## [2026-06-12T12:17:53.714Z]
Removed two no-op identity .map() calls after .sort() in buildCommonCanonical and computeLlmInputHash. These calls re-allocated identical tuples without transforming the data, adding noise to a frozen module. Behavior is unchanged; golden hash tests confirm the canonical serialization contract is intact. Extended codePointCanonicalJson JSDoc with three clarifying notes: key ordering is UTF-16 code-unit order (all real keys are ASCII so astral-plane behavior is out of scope); callers must pass finite numbers (NaN/Infinity stringify to null); undefined is dropped from objects while array elements must never be undefined.
## [2026-06-13T05:33:44.939Z]
The deterministic verifier now records and re-checks the result of every probe a check makes beyond its own subject files — directory listings, negative existence checks, graph-set membership, and sibling reads. The recorded observation carries the actual result that was seen, so a cached pass cannot survive a change to anything the check actually observed, only to its own subject files. This closes the gap where a check could read a directory or test for a sibling's existence, pass, and then keep passing after that surrounding state changed.
