一度 commit した 主張は、 chain 破壊 なしには 消せない、 書き換えられない、 順序が 変えられない。 LLM は 自分の call を 跨いで 「過去の 判定を そのまま 保持する」 保証を 出せない (read-write な memory 上の 主張は 常に 再解釈可能)。 ledger は その 反対の 部品。
scripts/checker/ledger.ts — SHA-256 chain runtime (init / append / verify / query) scripts/checker/ledger-guard-binding.ts — refactor-guard を ledger に bind する (spec digest ベース) scripts/checker/ledger-bootstrap.ts — CLI: bootstrap / --verify / --show data/checker-ledger/entries.jsonl — 実 ledger (append-only JSONL、 genesis + guard bindings) test/step1638-hash-chained-ledger-test.ts — 73/73 PASS (17 section)
each entry = {
seq: 0, 1, 2, … (monotonic)
timestamp: ISO 8601 UTC
kind: "genesis" | "refactor-guard-bound" | "verdict" | ...
payload: arbitrary JSON (kind-specific)
prevHash: sha256 of previous entry (or 64 zeros for genesis)
hash: sha256(seq + timestamp + kind + canonicalJSON(payload) + prevHash)
}
Genesis (seq=0) の prevHash は 64 個の 0。 以降の entry は 直前 entry の hash を prevHash として 連鎖。
canonicalJSON は key 順序 独立 serialise で、 同じ payload は 常に 同じ bytes に なる (hash 安定性)。
| 操作 | 検出される 違反 | test section |
|---|---|---|
| 過去 entry の payload を 書き換え | hash-mismatch | 4 |
| 中間 entry を 削除 | prev-hash-broken + sequence-gap | 5 |
| 偽 entry を 挿入 (捏造した hash で) | hash-mismatch | 6 |
| JSONL 破損 | parse-error | — |
ledger は 編集を 防ぐ tool ではない — file system は 相変わらず 書き換え可能。 ledger は 編集を 検出する tool — 誰かが 何を どこで 変えたか、 verify で 一発で 見える。
kind: "refactor-guard-bound" の payload は:
{
name: string, // spec.name (kebab-case unique)
stepOrigin: number, // spec.stepOrigin (病理を fix した STEP)
specDigest: string, // sha256 of structural fields
sourceModulePath: string, // repo-relative path of the .guard.ts file
boundAt: string, // ISO 8601
}
specDigest は 意図的に run() / extract() / description を 除外。
driver の 変更 (mock oracle の 更新、 helper rename) は 頻繁で 正当な 進化。
一方 意味を 変える structural fields (name / stepOrigin / invariantKind / invariantExpected / sweepVariable / sweepValues / pathologyShape / correctShape) の 変更は digest に 反映される。
| Status | 意味 | test section |
|---|---|---|
| intact | live spec が name で 見つかり、 digest が ledger と 一致 | 11 |
| drifted | live spec は 見つかるが structural digest が 異なる → 意図的なら re-bind で 新 entry を append | 13 |
| missing | ledger は 名前を 覚えているが live registry に 存在しない → guard test が silently 削除された | 12 |
これが 第 3 段 の 核: refactor-guard test を 単独 file として 存続に 依拠する 代わりに、 ledger に bind すると
「誰かが test file を 削除しても ledger は 覚えている」 = 削除は verify で 即座に verdict = missing に 変わる。
時間軸の 保証が sensor 単体から ledger 経由の 抽象へ 昇格。
$ npm run checker:ledger-bootstrap
> tsx scripts/checker/ledger-bootstrap.ts
ledger bootstrap: 1 new binding(s), 0 already present, path=data/checker-ledger/entries.jsonl
$ npm run checker:ledger-verify
> tsx scripts/checker/ledger-bootstrap.ts --verify
chain: ok=true entries=2
bindings: ok=true total=1 intact=1 drifted=0 missing=0
INTACT: harvest-lag-window-compared-does-not-follow-actual-retention (bound at seq 1, digest matches live spec)
$ npm run checker:ledger-show
> tsx scripts/checker/ledger-bootstrap.ts --show
{"seq":0, "kind":"genesis", "prevHash":"000...000", "hash":"e421...0364", ...}
{"seq":1, "kind":"refactor-guard-bound", "prevHash":"e421...0364", "hash":"b797...a9d0",
"payload":{"name":"harvest-lag-window-compared-does-not-follow-actual-retention",
"stepOrigin":1624,
"specDigest":"4e94...1cb3",
"sourceModulePath":"test/refactor-guards/step1624-window-compared.guard.ts",
"boundAt":"2026-08-31T15:23:02.105Z"}}
verdict: PASS
STEP 1624 guard が 実 ledger の seq=1 に bind され、 genesis からの chain 整合が verify OK。
以降 誰かが test/refactor-guards/step1624-window-compared.guard.ts を 削除すると、
npm run checker:ledger-verify が MISSING: harvest-lag-window-compared-... を 返し exit code 1 で CI が 落ちる。
Section 9 で 20 並列 append を 検証。 lock file (data/checker-ledger/entries.jsonl.lock) 取得 → last entry read → hash 計算 → 追記 → lock 解放 の atomic 化で、 seq が 1..20 の 完全な 順列に。
multi-tab 併走の race を 予防 (STEP 1554 atomic-commit と 同 pattern)。
| STEP 1632 arbitrariness_audit | STEP 1635 refactor-guard | STEP 1638 hash-chained ledger | |
|---|---|---|---|
| 次元 | spatial (今) | temporal (時間軸) | irreversibility (書換禁止) |
| 質問 | 「置いた数が どこに あるか」 | 「過去 病理 shape に 戻ったか」 | 「過去の 判定が silently 消された・変えられた・並べ替えられたか」 |
| 実装 | 静的 regex 分類器 | runtime invariance checker | SHA-256 chain + guard binding |
| trigger | inline literal 追加 | 病理 shape 復活 | 台帳 tampering / guard 削除 |
| 依拠 | source file 存続 | test file 存続 | ledger file 存続 + chain 整合 |
| run | test:step1632 | test:step1635 | test:step1638 + checker:ledger-verify |
3 段全て CI wire で、 LLM が call 単位で 出せない invariant の 3 種類 (今の 状態 / 時間跨ぎ shape 保持 / 過去書換禁止) を 静的な 部品として repo に 常駐させる。 「保証は 外部からしか 来ない」 の operational form 3 部品 が これで 揃う。
rm data/checker-ledger/entries.jsonl すれば ledger は 消える。 hash chain は 「編集を 検出する」 が 「消去 を 防ぐ」 ものではない。 外部 push (git commit + remote) が 補完する 前提。checker:ledger-verify を hook に 追加、 verify FAIL で commit block。 chain integrity を CI 側でも 別途 verify。data/step-counter.json) を ledger 化、 「STEP N は誰が いつ claim した」 の 台帳を 立ち上げる。scripts/checker/ledger.ts (~230 lines) + ledger-guard-binding.ts (~150 lines) + ledger-bootstrap.ts (~70 lines)test/step1638-hash-chained-ledger-test.ts (73/73 PASS, 17 section)data/checker-ledger/entries.jsonl (bootstrapped 2 entries)