## [2026-06-12T11:33:08.168Z]
Implements the lock-store I/O adapter: readLock (synchronous, absent file returns empty lock, unrecognized version or garbled content throws LockInvalidError fail-closed), serializeLock (canonical form with code-point sorted keys at every level, one verdict/node entry per line, trailing newline — pinned by golden test), and writeLock (serialize then atomic write via the existing atomic-write helper). The LockInvalidError message follows the what/why/next structure with specific handling for git conflict markers (take-a-side procedure) and both generic recoveries (restore from git / delete and re-verify). Written additively — not yet wired into the check or fill pipelines.
## [2026-06-12T11:45:55.506Z]
Conflict-marker detection in readLock was changed from a substring check (raw.includes('<<<<<<<')) to a line-anchored regex (/^<<<<<<< /m.test(raw)). The motivation: verdict reason strings may legitimately contain the text '<<<<<<< HEAD' when a reviewer quotes merge-conflict fragments from the source being checked. A substring match would throw LockInvalidError on a valid, self-written lock file in that case. The line-anchored form matches only real git conflict markers, which always start a line and are immediately followed by a space — a JSON-escaped string value inside the file cannot satisfy this pattern. The behavioral guarantee is unchanged for actual conflict-markered files: a file with a real unresolved merge conflict still throws.
## [2026-06-13T05:33:52.523Z]
A structurally malformed lock now fails closed with a clear recovery message instead of being silently treated as empty, so a corrupted lock can never be mistaken for an absent one and quietly re-approved over unreviewed code. Detection covers all three git conflict-marker families (the opening, separator, and closing marker forms) so a lock left mid-merge is recognised as conflicted rather than parsed as data.
## [2026-06-13T09:17:41.381Z]
The error raised when the verdict lock is unparseable carries a structured what/why/next message that the command-line layer renders for the user. The engine layer must not format that message itself — formatting is the command layer's job — so the error's internal fallback text is now just the one-line headline rather than the fully-assembled message. The structured data the command layer renders is unchanged; only the engine-side string construction was removed, keeping message formatting in a single layer.
