From 42024279b9a9d3e912e2506b60e021e1f85e4963 Mon Sep 17 00:00:00 2001 From: Scott Karp Date: Thu, 15 Oct 2026 15:01:57 +0000 Subject: [PATCH] manna: written by the system's own hand each morning; Friday's double stands on Shabbat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shemot 16: dvar yom b'yomo, and on the sixth day double, and none on the seventh. Every night at 00:00Z the day's provision went stale and the readiness probe failed until someone woke — 13.5 hours on 2026-10-15. CronJob `manna` (deploy/k8s/manna-cronjob.yaml) runs at 00:01Z Sunday to Friday in our own korban-processor image, mounts the manna PVC as the app's uid, and writes today.json from the ConfigMap's MANNA_* amounts — doubled on Friday, valid to Sunday 00:30Z. The pipeline now accepts Friday's `double` on Saturday (validate_date/3) and nothing else across a day boundary; three tests. The kohen sets the amounts, may still write by hand, and reads a failed job as the Day-4 alarm (KOHEN "The manna, each morning"; Tamid §1.1). Doorpost re-pinned. Elixir green. Co-Authored-By: Claude Fable 5.1 --- KOHEN.md | 21 ++++- deploy/k8s/configmap.yaml | 10 +++ deploy/k8s/manna-cronjob.yaml | 96 +++++++++++++++++++++++ specs/bikoret_tamid.md | 8 ++ vav_pipeline/lib/vav_pipeline/doorpost.ex | 2 +- vav_pipeline/lib/vav_pipeline/manna.ex | 26 ++++-- vav_pipeline/test/manna_shabbat_test.exs | 36 +++++++++ 7 files changed, 189 insertions(+), 10 deletions(-) create mode 100644 deploy/k8s/manna-cronjob.yaml create mode 100644 vav_pipeline/test/manna_shabbat_test.exs diff --git a/KOHEN.md b/KOHEN.md index 1149b3b5..0224f77e 100644 --- a/KOHEN.md +++ b/KOHEN.md @@ -625,7 +625,26 @@ counts move without a commit to the readings is the finding. ### Friday before sunset — *Erev Shabbat* -Write a **double portion** manna provision (`type: "double"`, doubled compute_units). The system will cease processing at Shabbat onset. No provision is written on Shabbat. +The manna CronJob writes Friday's provision as a **double portion** +(`type: "double"`, doubled compute units and external calls, valid to +Sunday 00:30Z); the pipeline accepts Friday's double on Saturday and +nothing else across a day boundary. The system ceases processing at +Shabbat onset. No provision is written on Shabbat — the CronJob does not +run on Saturday. + +### The manna, each morning — *Dvar yom b'yomo* + +Since 2026-10-15 the day's provision is written by the system's own hand: +CronJob `manna` (`deploy/k8s/manna-cronjob.yaml`) runs at 00:01Z Sunday +to Friday, mounts the manna PVC and writes `today.json` with the amounts +from `shorashim-config` (`MANNA_COMPUTE_UNITS` …). Before it, every night +at 00:00Z the old provision went stale and the readiness probe failed +until someone woke (13.5 hours on 2026-10-15). The kohen's acts now: + +- set the amounts in the ConfigMap (`kubectl apply -f deploy/k8s/configmap.yaml`); +- read `kubectl -n shorashim get jobs` after 00:01Z — a failed job is the + Day-4 alarm; write by hand with `kubectl cp` (Day 4) if it failed; +- run it at once when needed: `kubectl -n shorashim create job --from=cronjob/manna manna-now`. ### If the system halts diff --git a/deploy/k8s/configmap.yaml b/deploy/k8s/configmap.yaml index 738fa467..e7dabbac 100644 --- a/deploy/k8s/configmap.yaml +++ b/deploy/k8s/configmap.yaml @@ -12,6 +12,16 @@ data: # Manna provision file location MANNA_PROVISION_FILE: "/var/lib/bikoret/manna/today.json" + # The day's portion, written by the manna CronJob at 00:01Z Sunday to + # Friday (manna-cronjob.yaml); Friday's is doubled and stands through + # Shabbat. The kohen sets the amounts here (Pekudei: from the half-shekel + # fund); a change takes effect at the next writing. + MANNA_COMPUTE_UNITS: "1000" + MANNA_MEMORY_MB: "32768" + MANNA_API_CALLS_EXTERNAL: "500" + MANNA_STORAGE_DELTA_MB: "100" + MANNA_SANHEDRIN_PANELS_MAX: "5" + # Talmudic Engine socket TALMUDIC_SOCKET_PATH: "/tmp/bikoret/talmudic.sock" diff --git a/deploy/k8s/manna-cronjob.yaml b/deploy/k8s/manna-cronjob.yaml new file mode 100644 index 00000000..559a219b --- /dev/null +++ b/deploy/k8s/manna-cronjob.yaml @@ -0,0 +1,96 @@ +# The manna — written each morning by the system's own hand (Shemot 16). +# +# Until 2026-10-15 the day's provision was written by the kohen with +# `kubectl cp`, and every night at 00:00Z the old one went stale and the +# readiness probe failed until someone woke (13.5 hours on 2026-10-15). +# This CronJob writes today.json onto the manna PVC at 00:01Z, Sunday to +# Friday — a double portion on Friday that is valid through Shabbat, and +# nothing on Shabbat (Shemot 16:22–26: "on the sixth day they gathered +# double … on the seventh day there shall be none"). The kohen still sets +# the amounts (shorashim-config, MANNA_* below) and may still write a +# provision by hand at any hour; the CronJob only ends the gap. +# +# It runs our own korban-processor image (python, pinned to a commit tag) +# with the PVC mounted, as the app's uid/gid, so the file is readable by +# the pod; nothing else, no cluster permissions. ReadWriteOnce is fine: +# the cluster has one node and the job runs beside the pod. +apiVersion: batch/v1 +kind: CronJob +metadata: + name: manna + namespace: shorashim + labels: + app.kubernetes.io/name: shorashim + app.kubernetes.io/component: manna +spec: + schedule: "1 0 * * 0-5" # 00:01Z, Sunday–Friday; none on Shabbat + timeZone: "Etc/UTC" + concurrencyPolicy: Forbid + startingDeadlineSeconds: 3600 # a missed 00:01 still runs within the hour + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 3 + jobTemplate: + spec: + backoffLimit: 2 + template: + metadata: + labels: + app.kubernetes.io/name: shorashim + app.kubernetes.io/component: manna + spec: + restartPolicy: Never + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + imagePullSecrets: + - name: shorashim-registry + volumes: + - name: manna + persistentVolumeClaim: + claimName: manna-pvc + containers: + - name: write-manna + image: exo.container-registry.com/the-recognition-protocol/korban-processor:c0d0555a + imagePullPolicy: IfNotPresent + envFrom: + - configMapRef: + name: shorashim-config + volumeMounts: + - name: manna + mountPath: /var/lib/bikoret/manna + resources: + requests: { cpu: "20m", memory: "32Mi" } + limits: { cpu: "200m", memory: "128Mi" } + command: ["python3", "-c"] + args: + - | + import json, os, datetime as dt + now = dt.datetime.now(dt.timezone.utc) + today = now.date() + friday = today.weekday() == 4 + units = int(os.environ.get("MANNA_COMPUTE_UNITS", "1000")) + provision = { + "compute_units": units * (2 if friday else 1), + "memory_mb": int(os.environ.get("MANNA_MEMORY_MB", "32768")), + "api_calls_external": int(os.environ.get("MANNA_API_CALLS_EXTERNAL", "500")) * (2 if friday else 1), + "storage_delta_mb": int(os.environ.get("MANNA_STORAGE_DELTA_MB", "100")), + "sanhedrin_panels_max": int(os.environ.get("MANNA_SANHEDRIN_PANELS_MAX", "5")), + } + # Regular: through the day. Double (Friday): through Shabbat, to Sunday 00:30Z. + expires = (dt.datetime.combine(today + dt.timedelta(days=2), dt.time(0, 30), dt.timezone.utc) + if friday else dt.datetime.combine(today + dt.timedelta(days=1), dt.time(0, 30), dt.timezone.utc)) + doc = { + "date": today.isoformat(), + "provision": provision, + "type": "double" if friday else "regular", + "expires_at": expires.strftime("%Y-%m-%dT%H:%M:%SZ"), + "written_by": "manna-cronjob", + "written_at": now.strftime("%Y-%m-%dT%H:%M:%SZ"), + } + path = os.environ.get("MANNA_PROVISION_FILE", "/var/lib/bikoret/manna/today.json") + tmp = path + ".tmp" + with open(tmp, "w") as fh: + json.dump(doc, fh, indent=2) + os.replace(tmp, path) + print("manna written", doc["date"], doc["type"], "expires", doc["expires_at"]) diff --git a/specs/bikoret_tamid.md b/specs/bikoret_tamid.md index 93c462cb..f4458262 100644 --- a/specs/bikoret_tamid.md +++ b/specs/bikoret_tamid.md @@ -365,6 +365,14 @@ Errors: as /courtyard/bedikah, with "side 'a': …" / "side 'b': …" in the message; a missing side is 422. ``` +**The manna is written by the system's own hand** (2026-10-15): CronJob +`manna` at 00:01Z Sunday–Friday writes the day's provision onto the manna +PVC from the ConfigMap's `MANNA_*` amounts; Friday's is a double portion +that the pipeline accepts on Shabbat (Shemot 16:22–26); nothing is +written on Shabbat, nothing else crosses a day boundary. The kohen sets +the amounts and may still write by hand; a failed job is the Day-4 alarm +(KOHEN "The manna, each morning"). + **The names that still carry the old word** (renamed 2026-10-13, PRD §0). Everything the specification, the code, the images and the API say is *Bikoret*: headers `X-Bikoret-*`, the `bikoret` object on a gate reply, diff --git a/vav_pipeline/lib/vav_pipeline/doorpost.ex b/vav_pipeline/lib/vav_pipeline/doorpost.ex index 4c1fceef..453460d7 100644 --- a/vav_pipeline/lib/vav_pipeline/doorpost.ex +++ b/vav_pipeline/lib/vav_pipeline/doorpost.ex @@ -180,7 +180,7 @@ defmodule VavPipeline.Doorpost do # changes; the update is a deliberate operator commit. A bare # hash-update commit (without a corresponding spec change in the # same commit) is itself a drift signal. - @expected_hash "10735ab4e4280bf35d19d6ed79804c31d5ef199a70a289f455e138085e9cba5d" + @expected_hash "65bf08fc3c22307eb10ea6833ddeb475b1f6d87f145901e8d1b426c544c523be" @doc "Return the list of files that constitute the category-grade specification." @spec files() :: [String.t()] diff --git a/vav_pipeline/lib/vav_pipeline/manna.ex b/vav_pipeline/lib/vav_pipeline/manna.ex index c39ed4a9..16846ff2 100644 --- a/vav_pipeline/lib/vav_pipeline/manna.ex +++ b/vav_pipeline/lib/vav_pipeline/manna.ex @@ -253,7 +253,7 @@ defmodule VavPipeline.Manna do end defp validate_manna(data, now) do - with {:ok, date} <- validate_date(data["date"], now), + with {:ok, date} <- validate_date(data["date"], now, parse_type(data["type"])), {:ok, provision} <- validate_provision(data["provision"]), {:ok, expires_at} <- parse_expires_at(data["expires_at"]), :ok <- check_expiry(expires_at, now) do @@ -269,15 +269,25 @@ defmodule VavPipeline.Manna do end end - defp validate_date(nil, _now), do: {:error, "MANNA_INVALID: missing date"} + defp validate_date(nil, _now, _type), do: {:error, "MANNA_INVALID: missing date"} - defp validate_date(date_str, now) do - today = DateTime.to_date(now) |> Date.to_iso8601() + # A provision is for its day — except the double portion: gathered on + # Friday, it is the Shabbat's too (Shemot 16:22–26), so Friday's + # `double` stands on Saturday. Nothing else carries over the boundary. + defp validate_date(date_str, now, type) do + today = DateTime.to_date(now) + today_str = Date.to_iso8601(today) + yesterday = Date.add(today, -1) - if date_str == today do - {:ok, date_str} - else - {:error, "MANNA_STALE: provision is for #{date_str}, today is #{today}"} + cond do + date_str == today_str -> + {:ok, date_str} + + type == :double and Date.day_of_week(today) == 6 and date_str == Date.to_iso8601(yesterday) -> + {:ok, date_str} + + true -> + {:error, "MANNA_STALE: provision is for #{date_str}, today is #{today_str}"} end end diff --git a/vav_pipeline/test/manna_shabbat_test.exs b/vav_pipeline/test/manna_shabbat_test.exs new file mode 100644 index 00000000..ffa1b071 --- /dev/null +++ b/vav_pipeline/test/manna_shabbat_test.exs @@ -0,0 +1,36 @@ +defmodule VavPipeline.MannaShabbatTest do + @moduledoc "Shemot 16:22–26: Friday's double portion is the Shabbat's too; nothing else crosses the day boundary." + use ExUnit.Case, async: false + alias VavPipeline.Manna + + @friday ~D[2026-10-16] + @saturday ~D[2026-10-17] + @sunday ~D[2026-10-18] + + defp write(date, type, expires) do + path = Manna.provision_file() + File.mkdir_p!(Path.dirname(path)) + File.write!(path, Jason.encode!(%{"date" => Date.to_iso8601(date), "provision" => %{"compute_units" => 2000, "memory_mb" => 1, "api_calls_external" => 1, "storage_delta_mb" => 1, "sanhedrin_panels_max" => 1}, "type" => type, "expires_at" => expires, "written_by" => "test", "written_at" => "2026-10-16T00:01:00Z"})) + on_exit(fn -> File.rm(path) end) + end + + defp at(date, h), do: DateTime.new!(date, Time.new!(h, 0, 0), "Etc/UTC") + + test "Friday's double stands on Saturday" do + write(@friday, "double", "2026-10-18T00:30:00Z") + assert {:ok, %{type: :double}} = Manna.check_at(at(@friday, 12)) + assert {:ok, %{type: :double}} = Manna.check_at(at(@saturday, 12)) + end + + test "a regular provision does not cross the boundary; a double does not reach Sunday" do + write(@friday, "regular", "2026-10-18T00:30:00Z") + assert {:error, "MANNA_STALE" <> _} = Manna.check_at(at(@saturday, 12)) + write(@friday, "double", "2026-10-19T00:30:00Z") + assert {:error, "MANNA_STALE" <> _} = Manna.check_at(at(@sunday, 12)) + end + + test "a double written on a Thursday does not carry to Friday — only Shabbat receives it" do + write(~D[2026-10-15], "double", "2026-10-17T00:30:00Z") + assert {:error, "MANNA_STALE" <> _} = Manna.check_at(at(@friday, 12)) + end +end