Compare commits

...
Author SHA1 Message Date
Scott KarpandClaude Fable 5.1 bbf151957f doorpost: move the pin for the indexed middot (02b562ec…)
f6651425 rewrote binyan_av.pl, gezerah_shavah.pl and shnei_ketuvim.pl
— all three Doorpost files — without moving @expected_hash: the slip
of tablet 54, made again, and this time caught by
scripts/doorpost_check.py in build_and_push before any image was built
(main carried the drift for eleven minutes; no pod ever did). The pin
moves to the indexed middot's hash. doorpost_test, anchors_test,
sugya_test 41/0.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-25 02:19:49 +00:00
Scott KarpandClaude Fable 5.1 f6651425ff engine: the verses indexed by root — the pairwise middot read, not scan
Over the whole scroll (861cd4e6) gezerah shavah, binyan av and shnei
ketuvim scanned verse pairs — quadratic over 5,853 verses, 2.9 s in the
middot suite and waiting to be a live cost the day a middah is
consulted. The generator now also writes root_in(Root, Book, Ch, Vs),
one fact per root occurrence (49,542), exported by torah_text; the
pairwise rules read the first verse and find the second (and third) by
the root's index: shared_keyword, binyan_av_two, verses_contradict,
resolving_verse; verses_mentioning_root is the index itself. Same
answers (middot 47/47, phase3 28/28 unchanged), middot suite 2.865 s →
0.112 s. l1_sha256 unchanged (the facts are derived from the same L1);
--check passes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-25 02:16:08 +00:00
Scott KarpandClaude Fable 5.1 94ec324cc0 deploy: two replicas, surge rollout, requests sized to use
A pod that is not ready took every caller behind the gate dark, and
every rollout this week left a five-minute 503 in the watchdog log.
Two replicas with maxSurge 1 / maxUnavailable 0 keep one Ready pod
through a rollout or a crash. One node: this covers rollouts and pod
failures, not the node.

The node has 1700m CPU allocatable with 1470m requested; a second pod
at the old 450m request could not schedule (tablet 31 saw the surge
pod hang Pending). The pod USES 22m (vav 20m, sidecars 2m, measured),
so requests move to what is used with headroom: vav 120m/256Mi,
sidecars 40m/64Mi — 200m per pod, 400m for two, less than the one pod
asked before. Limits unchanged. POOL_SIZE 10 → 5 per pod (10 in all,
under the hobbyist tier's ceiling). KOHEN "Rolling out" updated.

Both manifests validate (kubectl --dry-run=client). Doorpost pin
unchanged. Applying needs the transmitter (or the classifier off).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-25 02:04:39 +00:00
10 changed files with 49596 additions and 32 deletions

No files matched your search

+3 -1
View File
@@ -599,7 +599,9 @@ aligned, not that the output is true. Read `limits` in the response.
### Rolling out — *Nesi'at haMishkan*
`scripts/shorashim-rollout` (also installed at `~/.local/bin/`) restarts
the deployment, waits for 3/3, and takes two consecutive 200s from
the deployment, waits for both pods (two replicas since 2026-10-25,
surge rollout: one pod stays Ready throughout, so a rollout no longer
shows in the watchdog log), and takes two consecutive 200s from
`/kodesh/health` before reporting — a rollout is not done at 3/3 (NLB
re-admission lag). A new image must already be pushed as `:latest`. If
the day's manna has not been written, the new pod hangs at 2/3 until it
+2 -2
View File
@@ -66,5 +66,5 @@ data:
# a pool of 20 fills its connection ceiling by itself (2026-10-15:
# refusals "remaining connection slots are reserved" for 24 h, one
# pool worker looping, Aiven's monitor refused). Ten is ample for a
# single replica.
POOL_SIZE: "10"
# single replica; two replicas since 2026-10-25 → 5 each, 10 in all.
POOL_SIZE: "5"
+21 -14
View File
@@ -18,18 +18,25 @@ metadata:
app.kubernetes.io/name: shorashim
app.kubernetes.io/version: "0.1.0"
spec:
replicas: 1
# One replica on one node with no room to surge. The default
# (maxSurge 25% → 1 extra pod) asks for 450m CPU the node does not
# have beside the running pod, and the rollout hangs Pending while the
# old image keeps serving (tablet 31, 2026-10-04). Replace in place.
# The new pod reads the day's manna from the manna-pvc volume; it is
# Ready again without the kohen, as long as today's portion was written.
# Two replicas (2026-10-25). A pod that is not ready took every caller
# behind the gate dark, and every rollout left a five-minute 503 in
# the watchdog log; with two pods and a surge rollout the Service keeps
# one Ready pod throughout. One node: this covers rollouts and pod
# crashes, not the node. The node has 1700m CPU allocatable and 1470m
# was requested (tablet 31, 2026-10-04: a surge pod at 450m hung
# Pending), so the requests below are sized to what the pod USES
# (vav 20m, sidecars 2m, measured) with headroom — 200m per pod, 400m
# for two — not to the old 450m. Limits are unchanged. Each pod reads
# the day's manna from the manna-pvc volume and is Ready without the
# kohen as long as today's portion was written; the database pool is
# 5 per pod (POOL_SIZE in the ConfigMap), 10 in all, under the
# hobbyist tier's ceiling.
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 0
maxUnavailable: 1
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app.kubernetes.io/name: shorashim
@@ -237,7 +244,7 @@ spec:
resources:
requests:
memory: "256Mi"
cpu: "250m"
cpu: "120m"
limits:
memory: "1Gi"
cpu: "1000m"
@@ -293,8 +300,8 @@ spec:
mountPath: /tmp
resources:
requests:
memory: "128Mi"
cpu: "100m"
memory: "64Mi"
cpu: "40m"
limits:
memory: "512Mi"
cpu: "500m"
@@ -318,8 +325,8 @@ spec:
mountPath: /tmp
resources:
requests:
memory: "128Mi"
cpu: "100m"
memory: "64Mi"
cpu: "40m"
limits:
memory: "512Mi"
cpu: "500m"
+9 -1
View File
@@ -59,6 +59,7 @@ def verse_files():
def build():
h = hashlib.sha256()
facts = []
index = [] # root_in/4: the verses by root — what the pairwise middot read instead of scanning (2026-10-25)
nverses = nroots = 0
for book, ch, vs, path in verse_files():
data = open(path, "rb").read()
@@ -72,6 +73,8 @@ def build():
roots.append(a)
gloss = " ".join(w.get("consonants", "") for w in d.get("words", []) if w.get("consonants"))
facts.append(f"verse({book}, {ch}, {vs}, [{', '.join(roots)}], {quote(gloss)}).")
for r in roots:
index.append(f"root_in({r}, {book}, {ch}, {vs}).")
nverses += 1
nroots += len(roots)
header = f"""%% l1_verses.pl — GENERATED by scripts/build_engine_verses.py from l1/torah.
@@ -82,8 +85,13 @@ def build():
%%
%% l1_sha256: {h.hexdigest()}
%% verses: {nverses} root_occurrences: {nroots}
%%
%% root_in(Root, Book, Ch, Vs) — the same facts inverted, one per root
%% occurrence, so a rule that asks "which verses carry this root" reads
%% an indexed table instead of scanning 5,853 verses (the pairwise middot:
%% gezerah shavah, binyan av, shnei ketuvim).
"""
return header + "\n".join(facts) + "\n", h.hexdigest(), nverses
return header + "\n".join(facts) + "\n\n" + "\n".join(index) + "\n", h.hexdigest(), nverses
def main():
+1 -2
View File
@@ -55,10 +55,9 @@ binyan_av_two(
CommonRoot
) :-
verse(B1, C1, V1, Roots1, _),
verse(B2, C2, V2, Roots2, _),
member(CommonRoot, Roots1),
member(CommonRoot, Roots2),
root_domain(CommonRoot, Domain),
root_in(CommonRoot, B2, C2, V2), % by the index, not a scan
ref(B1,C1,V1) \= ref(B2,C2,V2).
%% ============================================================
+4 -2
View File
@@ -25,11 +25,13 @@
%% shared_keyword(Verse1, Verse2, SharedRoot)
%% Two verses share a Hebrew root, creating a verbal link.
%% The first verse is read; the second is found by the root's index
%% (root_in/4), not by scanning the scroll (2026-10-25: over 5,853
%% verses the scan was quadratic).
shared_keyword(ref(Book1, Ch1, Vs1), ref(Book2, Ch2, Vs2), SharedRoot) :-
verse(Book1, Ch1, Vs1, Roots1, _),
verse(Book2, Ch2, Vs2, Roots2, _),
member(SharedRoot, Roots1),
member(SharedRoot, Roots2),
root_in(SharedRoot, Book2, Ch2, Vs2),
ref(Book1, Ch1, Vs1) \= ref(Book2, Ch2, Vs2).
%% ============================================================
+4 -5
View File
@@ -33,11 +33,10 @@
%% Verses contradict if their roots map to conflicting domains
verses_contradict(ref(B1,C1,V1), ref(B2,C2,V2), domain_conflict(D1, D2)) :-
verse(B1, C1, V1, Roots1, _),
verse(B2, C2, V2, Roots2, _),
member(R1, Roots1),
member(R2, Roots2),
root_domain(R1, D1),
root_domain(R2, D2),
root_in(R2, B2, C2, V2), % by the index, not a scan
domains_in_tension(D1, D2),
ref(B1,C1,V1) \= ref(B2,C2,V2).
@@ -62,10 +61,10 @@ domains_in_tension(covenant, authority).
resolving_verse(ref(B1,C1,V1), ref(B2,C2,V2), ref(B3,C3,V3)) :-
verse(B1, C1, V1, Roots1, _),
verse(B2, C2, V2, Roots2, _),
verse(B3, C3, V3, Roots3, _),
%% Third verse shares a root with first verse
%% Third verse shares a root with first verse — by the index
member(SharedRoot1, Roots1),
member(SharedRoot1, Roots3),
root_in(SharedRoot1, B3, C3, V3),
verse(B3, C3, V3, Roots3, _),
%% Third verse shares a root with second verse
member(SharedRoot2, Roots2),
member(SharedRoot2, Roots3),
File diff suppressed because it is too large. Load diff
@@ -10,6 +10,7 @@
:- module(torah_text, [
verse/5,
root_in/4,
verse_roots/4,
verses_in_chapter/3,
verses_mentioning_root/2
@@ -42,8 +43,6 @@ verse_roots(Book, Chapter, Verse, Roots) :-
verses_in_chapter(Book, Chapter, Count) :-
aggregate_all(count, verse(Book, Chapter, _, _, _), Count).
%% Find all verses mentioning a specific root
%% Find all verses mentioning a specific root — by the index.
verses_mentioning_root(Root, Verses) :-
findall(ref(Book, Ch, Vs),
(verse(Book, Ch, Vs, Roots, _), member(Root, Roots)),
Verses).
findall(ref(Book, Ch, Vs), root_in(Root, Book, Ch, Vs), Verses).
+1 -1
View File
@@ -182,7 +182,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 "7eac65cfadccc37e600a779fc89acef0a24a9eac35e5ab18e8ec845cc1f104c2"
@expected_hash "02b562ecd0c8f0db20a1afe41504b2c30e2b1b12692cfbdf9cae86096de3bc7b"
@doc "Return the list of files that constitute the category-grade specification."
@spec files() :: [String.t()]