祝日テーブルは黙って腐る——「データの鮮度」を出力に運ぶ

Kairos

発端

年明け、締め処理の通知が元日に飛んだ。調べると、営業日判定に使っていた祝日テーブルに 2027 年分が入っていなかった。去年それを入れた人は、もう隣の部署にいる。

このバグの嫌なところは、コードが一行も間違っていないことだ。祝日を配列で持ち、 if (!holidays.includes(date)) で弾く。ロジックは正しい。ただ、データに賞味期限があることを、システムのどこにも書く場所がなかった。

cron には祝日という概念がそもそも無い(第 1 弾に書いた)。自前実装には概念はあるが、「このテーブルはいつまで有効か」を表現する型が無い。だからテーブルは黙って腐り、腐ったことは事故で分かる。

「どこまで知っているか」を宣言させる

Kairos では、データを持ち込むテーブルに covering(有効範囲)の宣言が付く。「この列は、この範囲について完全」という主張であり、主張の外を評価すると結果に範囲外註釈が並走する。

2027 年の祝日がまだ手元に無い、という例の状況をそのまま書いてみる。書き方は三通りある——そして三通りのうち二つは罠だ。

罠 その 1: 大きく主張する

holidays2027 = [] covering: 2027..2027
satSun = everyDay |> filter(d => weekday(d) == Sat or weekday(d) == Sun)
bizDay = everyDay \ (satSun | holidays2027)

bizDay

「2027 年の範囲で、空」。一見正直に見える。2027 年 1 月第 1 週を評価すると:

2027-01-04
2027-01-05
2027-01-06
2027-01-07
2027-01-08
# 被覆サマリ
#   holidays2027 covering 2027-01-01..2027-12-31 残走路 355 日

▶ Playground で開く

警告は何も出ない。当然で、covering: 2027..2027 は「2027 年を完全に把握しており、祝日はゼロ」という主張だからだ。「まだ届いていない」のつもりで書いた式が、言語には「調べ終わった。無かった」と聞こえている。元日(実在の祝日)は営業日として発火し、被覆サマリは「残走路 355 日」——このデータはまだ 355 日ぶん有効という偽の安心を返す。

主張した範囲の中身は、主張した本人しか保証できない。言語は宣言を信じることしかできない。

罠 その 2: 開けっぱなしにする

holidays2027 = [] covering: ..

範囲を書くから嘘になるのなら、開けておけばいいのでは。結果:

2027-01-04
2027-01-05
2027-01-06
2027-01-07
2027-01-08
# 被覆サマリ
#   holidays2027 covering ..(完結主張) 残走路 ∞

▶ Playground で開く

こちらはもっと悪い。covering: .. は「過去も未来も、恒久にこれで完結」という最強の主張で、残走路は ∞ に張り付く。「データが尽きかけたら警告する」という監視をどう組んでも、∞ が閾値を割る日は来ない。腐敗検知の経路が、宣言ひとつで恒久に塞がる。

正解: 知っているところまでしか主張しない

holidays2027 = [] covering: 2026-08-04..2026-08-04

「点はゼロ。保証するのは、確認した今日この日まで」。これが正直な書き方で、出力が変わる:

2027-01-04
2027-01-05
2027-01-06
2027-01-07
2027-01-08
# ⚠ 範囲外 2027-01-04..2027-01-11(holidays2027 covering 2026-08-04..2026-08-04)
# 被覆サマリ
#   holidays2027 covering 2026-08-04..2026-08-04 残走路 -159 日

▶ Playground で開く

点列は同じ 5 点。だが今度は ⚠ の範囲外註釈が並走する——「この結果は、祝日データが何も語れない区間で出している」。そして残走路は −159 日。「あと何日で尽きるか」ではなく「もう 159 日ぶん越えている」を、評価のたびに機械可読で告げる。

発火は止まらないことに注意してほしい。データが無いからといってシステムを止めるのはたいてい過剰反応で、土日を除いた点は出続ける。退化するが、退化していることが観測できる——これが求めていた性質だ。

監視は数字一本になる

残走路は「covering の終端 − 評価範囲の終端」。つまり評価がどこまで読むかに対して、データがどれだけ持つかという一本の数字だ。運用はこうなる:

  • 残走路 < 75 日 → 「祝日データの更新期限が近い」を警告に流す
  • 残走路 < 0 → 結果に註釈が出ている=更新が遅れている

「テーブルを最後に更新したのはいつか」を人間の記憶に頼るのではなく、評価のたびにデータ自身が答える。祝日テーブルが腐る問題は、こうして「事故で気づく」から「尽きる 75 日前に気づく」に変わる。

締め

前回、Kairos に希望する未来として「データが尽きる前に警告が出る」と書いた。その実体がこの二つ——covering(どこまで知っているかの宣言)と残走路(あとどれだけ持つかの数字)だ。仕掛けとしては地味で、派手な機能では全くない。ただ、祝日テーブルを一度でも腐らせたことのある人には、この地味さがいちばん効くと思っている。

(コード例は全て Playground でそのまま動く。言語仕様は kairos-lang.org——現在 RC・1.0 準備中。)

コメント

タイトルとURLをコピーしました