月曜の朝、いちばん答えにくい質問
週末にサーバーを止めた。電源工事でも、マイグレーションでも、ただの障害でもいい。月曜の朝、誰かがこう聞く——「止まってたあいだに、何が飛んでるはずだった?」
cron はこの質問に答えられない。crontab が持っているのは「次にいつ走るか」を計算する規則で、「あの 36 時間に何が走るはずだったか」を列挙する装置ではない。anacron や systemd timer の Persistent= は「取りこぼしたら起動後に 1 回走らせる」ところまでは面倒を見てくれるが、それは「1 回にまとめて走らせる」のであって、何回ぶん・いつのぶんを取りこぼしたのかは教えてくれない。給与振込のように「8/22 のぶん」と「8/23 のぶん」が別の意味を持つ処理では、「まとめて 1 回」は答えになっていない。
スケジュールが「定義」なら、過去も未来も同じ質問である
Kairos の式は「いつ発火するか」の集合の定義だ。定義は現在時刻を知らない。評価範囲 [from, to) を与えると、その範囲に落ちる発火が全部出てくる——範囲が未来なら「予定表」、範囲が過去なら「監査」、範囲が停止していた窓なら「撃ち漏らしの一覧」になる。同じ質問の向きを変えているだけで、答える仕組みは一つしかない。
毎朝 9 時のバッチが、8/22(土)から 8/24(月)朝の復旧まで止まっていたとする。止まっていた窓をそのまま評価範囲にすると:
premise JP {
calendar-system: Gregorian
tz: "Asia/Tokyo"
wkst: Mon
}
@JP
everyInstant |> strideBy(1d, from: 2026-01-05T09:00)
2026-08-22T09:00 2026-08-23T09:00
撃ち漏らしは 2 発、8/22 と 8/23 の 9 時ぶん。「復旧後に何をリカバリするか」はこのリストを見て決めればいい——1 件ずつ流すのか、最後の 1 件だけでいいのか、それは業務が決めることだが、少なくとも何を取りこぼしたかはもう議論にならない。
「いつ聞いても同じ答え」だから監査に使える
このやり方が成立するのは、評価が決定的だからだ。同じ定義・同じ範囲・同じデータなら、何度評価しても、いつ評価しても、答えは同じ。範囲をずらして重ねても、重なった部分は必ず一致する。給料日ルール(25 日・休日なら前営業日)で実測すると:
$ 前半年で評価 --from 2026-01-01 --to 2026-07-01 2026-01-23 2026-02-25 2026-03-25 2026-04-24 2026-05-25 2026-06-25 $ 4 月以降で評価 --from 2026-04-01 --to 2027-01-01 2026-04-24 2026-05-25 2026-06-25 2026-07-24 2026-08-25 …
4〜6 月は両方の範囲に含まれ、答えは一字違わず同じ(04-24・05-25・06-25)。当たり前に見えるが、cron ベースの「前回実行時刻+interval」型の実装はこの性質を持たない——再起動やリプレイのたびに位相がずれる。定義が状態を持たないから、監査もリプレイも安全になる。
機械にはそのまま JSON で渡す
この「範囲を与えて発火列をもらう」ループを道具にしたのが、リファレンス実装の CLI に入った list / next サブコマンドだ。next は「次の 1 発はいつか」——at や systemd に渡す 運用の最小口:
$ node src/cli.ts next -n 1 --json --from 2026-08-07 examples/payday.kairos
{
"command": "next",
"from": "2026-08-07",
"to": "2026-08-26",
"requested": 1,
"found": 1,
"results": [
{
"dates": ["2026-08-25"],
"points": [1787583600000],
"annotations": []
}
],
"coverage": [
{
"source": "holidays2026",
"covering": "2026-01-01..2026-12-31",
"concluded": false,
"runwayDays": 128
}
],
"warnings": []
}
(紙幅の都合で数フィールド省略。points は epoch ミリ秒——冒頭の停止窓のような「厳密には何時何分から何時何分まで止まっていた」との交差計算は、この生の数でやればいい。日付文字列を再パースする必要はない。)
見てほしいのは coverage だ。「次は 8/25」という答えに、その答えの賞味期限——祝日データは年内いっぱいで、残走路 128 日——が必ず併走する。第 5 弾で書いた「データの鮮度を出力に運ぶ」は、撃ち漏らしの一覧にも次回予告にも同じように付いてくる。答えと、答えをどこまで信じていいかが、ワンセットで返る。
ちなみに next は、地平線内に要求した本数が見つからないと終了コード 2 で帰る。「次の発火なし」を正常終了で黙って返すと、スケジューラ側は「もう何も来ない」と「取りこぼした」を区別できない——空の答えにも意味の区別がある、というのは撃ち漏らしの話と地続きである。
cron に訊けないのは「13 日の金曜」だけではなかった
第 1 弾で「月末 3 営業日前が書けない」から始めたこの連載は、気づけば書けないの話から訊けないの話に来ている。cron には未来の複雑な日付が書けないだけでなく、「先週、何が起きるはずだったか」という過去向きの質問が訊けない。スケジュールを実行状態の中にではなくレビューできる定義として持つことの実益は、たぶんこの質問に即答できることがいちばん分かりやすい。止まっていた 36 時間は、もう不安の種ではなくて、ただの評価範囲である。

コメント