あのテーブルを作ったことがある人へ
ジョブスケジューラの機能を作ったことがある人は、たぶんあのテーブルを持っている。名前は現場によって違う——スケジュールインスタンステーブル、実行予定表、コマンドリスト。中身は同じで、予定を先に展開して保存したものだ。毎晩のバッチが 3 ヶ月先まで予定行を生成し、期限が近づくとまた伸ばす。実行が済んだら消し込む。照合クエリが「予定にあって実績にない行」を探す。そして誰かが最初に決めたのだ——このテーブルは、未来永劫、記録し続けると。
なぜあれが要ったのか。cron 式は「次にいつ走るか」しか教えてくれないからだ。「あの日の予定は何だったか」を後から聞くには、予定を先に物質化して保存しておくしかなかった。予定は状態だった。だから設備が要った。
前回、止まっていた 36 時間は「ただの評価範囲である」と書いた。今回はその続きで、実装の側から同じことを言う——スケジュールが定義なら、予定側の保存はまるごと要らない。
予定は保存するものではなく、導出するものになる
定義は、同じ範囲・同じデータで評価すれば必ず同じ答えを返す。つまり「あの日の予定」はいつでも決定的に再計算できる——保存しておく理由がない。永続化が要るのは実績ログだけで、それも監査に必要な期間だけローテートで持てばいい。予実照合は、こうなる:
照合 = 定義の評価 × 実績ログの grep
実演する。支払日ルール(5・15・25 日、祝日は除く)で、祝日データは external 宣言——式は静的・データは実行時解決の差し込み口だ:
premise HRDB {
calendar-system: Gregorian
tz: "Asia/Tokyo"
wkst: Mon
source: "hr-db/holidays"
holidays = external(kind: dates)
}
@HRDB
gotobi = everyDay |> filter(d => dayNo(d) == 5 or dayNo(d) == 15 or dayNo(d) == 25)
gotobi \ holidays
祝日データは JSON で手元に置く(人事 DB から吐いたと思ってほしい):
{
"holidays": {
"dates": ["2026-01-01", "2026-01-12", "2026-02-11", "2026-02-23", "2026-03-20"],
"covering": "2026-01-01..2026-03-31",
"asof": "2026-01-05"
}
}
CLI に両方を渡すと、第 1 四半期の「予定」がその場で導出される:
$ node src/cli.ts list payments.kairos --from 2026-01-01 --to 2026-04-01 --supply supply.json 2026-01-05 2026-01-15 2026-01-25 2026-02-05 2026-02-15 2026-02-25 2026-03-05 2026-03-15 2026-03-25 # 被覆サマリ # HRDB.holidays covering 2026-01-01..2026-03-31 asof 2026-01-05 残走路 0 日
▶ Playground で規則部を開く (供給データの注入は CLI だけの機能にしてある。手元の業務データをブラウザに送らない——式がブラウザの外に出ない Playground と、同じ思想の裏返しだ)
あとは実績ログと突き合わせるだけ。3 月 15 日の支払いが実行されていなかったとすると:
$ node src/cli.ts list payments.kairos --from 2026-01-01 --to 2026-04-01 \
--supply supply.json | grep -v '^#' > planned.txt
$ comm -23 planned.txt actual.txt
2026-03-15
これが照合器の全実装である。 予定行の生成バッチも、期限延長も、消し込みも、専用テーブルもない。planned.txt は使い捨てで、消しても同じものがいつでも出てくる。
検査まで言語側に落ちる
もうひとつ、あのテーブルの周りには検査コードが住んでいた。取り込んだ祝日データは日付として正しいか、期間の主張と中身が食い違っていないか。external の解決値には、テーブルリテラルと同一の統治検査が契約として課される——たとえば供給パイプラインのバグで 2026-02-30 が 混入すると:
契約違反: 実在しない日付——external HRDB.holidays: 2026-02-30(黙ったロールオーバーの封止=ADR-43 の字句検査の解決値向け再執行。ADR-46)
3 月 2 日に読み替えられたりせず、言語境界で止まる。照合スクリプトに検査を写経する必要もない。そして出力の最後には毎回、被覆サマリが付いてくる——祝日データはいつ観測した版で、あと何日ぶんの主張が残っているか(第 5 弾に書いた「データの鮮度」だ)。予定の導出と、データの検査と、鮮度の申告が、全部同じ一回の評価から出てくる。
削除できる設備は、最高の設備である
まとめると、こうなる。
| 従来 | 定義ベース |
|---|---|
| 予定を N ヶ月ぶん展開して保存 | 保存しない(いつでも導出) |
| 生成バッチ・期限延長・バックフィル | ない |
| 照合クエリ+取り込み検査コード | 評価 × grep(検査は言語の契約) |
| 予実テーブルは未来永劫成長 | 実績ログだけ・ローテートで消える |
この連載を読んで「なんであの時これがなかったのか」と思った人がいたら、その苦さは正しい。私も同じものを作って、同じ照合クエリを書いて、同じテーブルを未来永劫伸ばしてきた。あれは当時の正解だった——予定が状態でしかあり得なかった時代の。
今からは、ある。予定は定義から導出するものになり、あのテーブルには墓標を立てていい。作らなくていい設備は、最高の設備である。

コメント