過去 の 病理 shape への 回帰を 静かに 許さない refactor guard の 実装装置。 spec は 宣言的 (pathology + correct shape + sweep values + invariant kind)、 driver は 各 guard 固有 (どう input を setup し、 どう output を extract するか)、 runtime は generic invariance checker (constant / monotonic / custom)。 registry で 一括 exec + 単独 test でも run 可能。
windowCompared = min(oracleReturned, perFeedDeclaredRetention) は 退役させた v0.2 病理と 形が 同一、 違いは 第二引数の 出自だけ (要件由来 vs 計器の 状態)、 安全性が その 一語に 全部 乗っている、 将来 だれかが 『簡略化』 で actualRetention に 戻したとき diff 上は 式が 変わったように 見えない、 コメントより test で 縛るほうが refactor を 生き延びる」
type RefactorGuardSpec<TOutput> = {
name: string; // kebab-case unique
stepOrigin: number; // 病理を fix した STEP
authoredAt: string; // ISO date
description: string;
pathologyShape: string; // 過去 病理 shape の 一文
correctShape: string; // 現在 correct shape の 一文
sweep: { variable, values }; // どの変数を どの値で 変化させるか
run: (v) => TOutput; // target を setup + 実行
extract: (o) => unknown; // check 対象の 値を 抜き出す
invariant:
| { kind: "constant"; expected; reason }
| { kind: "monotonic-increasing"; reason }
| { kind: "monotonic-decreasing"; reason }
| { kind: "custom"; check; reason };
};
runGuardSpec(spec) → { pass, observations, reason, ... }
runGuardRegistry(specs[]) → { results, passed, failed }
| kind | 意味 | 用途 |
|---|---|---|
constant | 全 sweep 値で extract() 結果が 一定 | STEP 1624 型 (「独立性」 の 表現) |
monotonic-increasing | sweep 順に 単調非減少 | 累積 count / progress |
monotonic-decreasing | sweep 順に 単調非増加 | 収束 / countdown |
custom | 任意 predicate on 抽出列 | 複雑 invariant / 集約制約 |
test/refactor-guards/step1624-window-compared.guard.ts が 最初の 具体 guard。 sweep = actualRetention ∈ {9, 10, 20, 50, 100}、 invariant = windowCompared === 9。
actualRet=9 windowCompared=9
actualRet=10 windowCompared=9
actualRet=20 windowCompared=9
actualRet=50 windowCompared=9
actualRet=100 windowCompared=9
verdict: PASS
windowCompared が sweep 全域で 一定 = perFeedDeclaredRetention (=9) から 導出される 正しい実装。
actualRet=9 windowCompared=9 ← 偶然 一致
actualRet=10 windowCompared=10 ← 病理 露呈
actualRet=20 windowCompared=20
actualRet=50 windowCompared=50
actualRet=100 windowCompared=100
verdict: FAIL — 「sequence tracks the sweep instead of staying at 9」
これが load-bearing evidence — guard は 正しく discriminate する。
未来の author が 「簡略化」 で min(oracleReturned, actualRetention) に 戻すと、
npm test で 即座に 落ちて 過去 incident に redirect される。
scripts/checker/refactor-guard.ts — runtime (~180 lines、 4 invariant kind、 error handling) test/refactor-guards/ └── step1624-window-compared.guard.ts — 最初の 具体 guard (STEP 1624 起源) test/step1635-refactor-guard-test.ts — 31/31 PASS (12 section) package.json: "test:step1635": "tsx test/step1635-refactor-guard-test.ts"
test/refactor-guards/stepN-<slug>.guard.ts を 新規作成、 export const spec: RefactorGuardSpec<T> を 定義npm run test:step1635 で 現行実装が PASS を verify| arbitrariness_audit (spatial) | refactor-guard (temporal) | |
|---|---|---|
| 質問 | 「今 sensor に arbitrary な数が 何個あるか」 | 「過去 の 病理 shape に silently 戻ったか」 |
| 実装 | 静的 regex 分類器 (source file scan) | runtime spec + sweep + invariance checker |
| 判定単位 | literal 単位 (declared==0 で PASS) | sweep 全体で invariant 保持 |
| trigger | 誰かが inline literal を 追加 | 誰かが 過去 病理 shape に 戻す |
| test runner | test:step1632 | test:step1635 |
両方 CI に wire すると、 「置いた数の 集約」 も 「時間を 跨いだ shape 保持」 も 機械が 見張る。 LLM が call 単位で 出せない invariant の 種類 (時間、 依存関係、 出自) を 静的な 部品として repo に 常駐させる 実装。
hash-chained claim ledger — d8_ledger_query (STEP 1402) の 上に append-only + hash 連鎖を 乗せ、 refactor guard test を ledger entry に bind。 「誰かが test file を silently 削除する」 も ledger との 整合 check で 検出可能に。 LLM が call を 跨いで 「過去の 判定を 書き換える」 を 構造的に 禁止。 本 STEP の 完了で 藤本さん の go 待ち。
scripts/checker/refactor-guard.ts (runtime、 ~180 lines)test/refactor-guards/step1624-window-compared.guard.tstest/step1635-refactor-guard-test.ts (31/31 PASS, 12 section)