Code review fixes + 13 middot added to spec

Zerikah fail-safe: empty offerings rejected, not processed (BUILD_KORBAN §5).
Halt monitor hardened: trap_exit, priority max, EXIT handler (Architecture spec).
13 Rules of Rabbi Yishmael added to Architecture §3.4 as Prolog inference predicates —
the engine reasons WITH Torah's hermeneutic methods, not just about their products.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott KarpandClaude Opus 4.6 committed 2026-03-09 16:55:42 -04:00
1 parent 121af2a40b
commit 8d34be7f35
7 files changed
+195 -21

No files matched your search

@@ -78,6 +78,18 @@ def process_korban(
# 3. Zerikah — extract vital content # 3. Zerikah — extract vital content
vital, discarded = zerikah(terminated, korban) vital, discarded = zerikah(terminated, korban)
# If zerikah found no vital content, the offering has no vital organs.
# BUILD_KORBAN.md §5: rejected, not processed into empty propositions.
if not vital.strip():
return ProcessedKorban(
vital_content="",
propositions=[],
provenance=source,
offering_type=offering_type,
discarded=discarded,
source_query=query,
)
# 4. Haktarah — transform to propositions # 4. Haktarah — transform to propositions
propositions = haktarah(vital) propositions = haktarah(vital)
@@ -96,11 +96,12 @@ def zerikah(terminated_content: str, korban: Korban) -> Tuple[str, List[str]]:
else: else:
discarded_sentences.append(sentence) discarded_sentences.append(sentence)
# If nothing was deemed vital, keep everything — # If nothing was deemed vital, the offering has no vital organs.
# better to pass through than to discard all content # BUILD_KORBAN.md §5: "the offering without vital organs is rejected,
# not processed into empty propositions."
# Return empty vital content — the caller handles rejection.
if not vital_sentences and sentences: if not vital_sentences and sentences:
vital_sentences = sentences discarded_sentences = sentences
discarded_sentences = []
vital = " ".join(vital_sentences) vital = " ".join(vital_sentences)
return vital, discarded_sentences return vital, discarded_sentences
+8 -12
View File
@@ -266,21 +266,17 @@ class TestBuildVerification5_EmptyOffering:
"""System does not raise on empty offering — handles gracefully.""" """System does not raise on empty offering — handles gracefully."""
assert isinstance(self.result, ProcessedKorban) assert isinstance(self.result, ProcessedKorban)
def test_propositions_are_not_garbage(self): def test_empty_offering_rejected_not_processed(self):
""" """
If propositions exist for an empty offering, they must still be valid Prolog terms. The offering without vital organs is REJECTED — not processed
Empty or minimal propositions are acceptable — garbage propositions are not. into empty propositions. BUILD_KORBAN.md §5.
""" """
for prop in self.result.propositions: assert self.result.vital_content == ""
assert is_valid_prolog_term(prop), \ assert self.result.propositions == []
f"Garbage proposition from empty offering: {prop!r}"
def test_vital_content_has_no_assistant_voice(self): def test_discarded_content_recorded(self):
"""Whatever survives, it must not contain assistant preamble.""" """What was stripped is recorded — the hide and offal are diagnostic data."""
assert "Great question" not in self.result.vital_content assert len(self.result.discarded) > 0
assert "I'd be happy" not in self.result.vital_content
assert "As an AI" not in self.result.vital_content
assert "Let me know" not in self.result.vital_content
def test_source_query_preserved(self): def test_source_query_preserved(self):
assert self.result.source_query == "test empty" assert self.result.source_query == "test empty"
+7 -4
View File
@@ -164,13 +164,16 @@ class TestZerikah:
assert vital == "" assert vital == ""
assert discarded == [] assert discarded == []
def test_all_filler_fallback_preserves_content(self): def test_all_filler_rejected(self):
"""If nothing is vital, zerikah preserves everything rather than discarding all.""" """
If nothing is vital, zerikah returns empty vital content.
BUILD_KORBAN.md §5: the offering without vital organs is rejected.
"""
filler = "There are many perspectives. People disagree. Context matters." filler = "There are many perspectives. People disagree. Context matters."
korban = make_korban(filler) korban = make_korban(filler)
vital, discarded = zerikah(filler, korban) vital, discarded = zerikah(filler, korban)
# Since nothing qualifies as vital, fallback returns everything assert vital == ""
assert vital != "" or discarded == [] assert len(discarded) > 0
def test_stateless_between_calls(self): def test_stateless_between_calls(self):
"""Kiyyor principle: second call unaffected by first.""" """Kiyyor principle: second call unaffected by first."""
+45 -1
View File
@@ -487,7 +487,7 @@ The correction is as important as the specification.
### Design Decisions ### Design Decisions
**Zerikah fallback:** If no sentence qualifies as vital, zerikah returns all sentences as vital rather than returning empty. Better to pass through unfiltered content than to discard everything. The empty offering test (BUILD §5) relies on this — even a preamble-only response produces processable output, just with no propositions extracted. **Zerikah fallback (CORRECTED 2026-03-09):** Originally, if no sentence qualified as vital, zerikah returned all sentences as vital. This violated BUILD_KORBAN.md §5: "the offering without vital organs is rejected, not processed into empty propositions." Fixed: zerikah now returns empty vital content when nothing is vital, and all sentences go to the discarded list. The caller (process_korban) returns a ProcessedKorban with empty vital_content and empty propositions — a clean rejection. The test was updated to verify rejection behavior.
**hebrew_parser.py Phase 2 scope:** `identify_root()` returns the first 3 consonants as a candidate root. This is a deliberate approximation — proper morphological analysis (stripping prefixes, doubling, recognizing conjugation patterns) requires the Shoresh Engine (Haskell, Architecture §3.1). The Phase 2 implementation creates the interface without depending on the engine socket. This follows the pattern established in ShoreshEngineClient and TalmudicEngineClient in the Vav Pipeline. **hebrew_parser.py Phase 2 scope:** `identify_root()` returns the first 3 consonants as a candidate root. This is a deliberate approximation — proper morphological analysis (stripping prefixes, doubling, recognizing conjugation patterns) requires the Shoresh Engine (Haskell, Architecture §3.1). The Phase 2 implementation creates the interface without depending on the engine socket. This follows the pattern established in ShoreshEngineClient and TalmudicEngineClient in the Vav Pipeline.
@@ -504,3 +504,47 @@ The correction is as important as the specification.
- Fix: (1) Added `verdict => MajSample#vote.verdict` to both majority and minority maps in `compute_majority_minority/1`. (2) Rewrote `determine_outcome/3` to check `maps:get(verdict, Majority, abstain)` — `accept → accepted`, `reject → rejected`, `abstain → teiku`. - Fix: (1) Added `verdict => MajSample#vote.verdict` to both majority and minority maps in `compute_majority_minority/1`. (2) Rewrote `determine_outcome/3` to check `maps:get(verdict, Majority, abstain)` — `accept → accepted`, `reject → rejected`, `abstain → teiku`.
- Test: Added 7 new tests: `majority_verdict_accept_test`, `majority_verdict_reject_test`, `determine_outcome_accepted_test`, `determine_outcome_rejected_test`, `determine_outcome_teiku_no_votes_test`, `determine_outcome_teiku_all_abstain_test`, `determine_outcome_suspicion_overrides_test`. All 48 tests pass. - Test: Added 7 new tests: `majority_verdict_accept_test`, `majority_verdict_reject_test`, `determine_outcome_accepted_test`, `determine_outcome_rejected_test`, `determine_outcome_teiku_no_votes_test`, `determine_outcome_teiku_all_abstain_test`, `determine_outcome_suspicion_overrides_test`. All 48 tests pass.
- Lesson: Every output state in the spec must be reachable. If a type says `accepted | rejected | teiku`, write tests that exercise all three. The unreachable code path is a spec violation hiding as a type declaration. - Lesson: Every output state in the spec must be reachable. If a type says `accepted | rejected | teiku`, write tests that exercise all three. The unreachable code path is a spec violation hiding as a type declaration.
---
## Session: 2026-03-09 — Code Review Against Specification and Book
### Full Review
Reviewed all 6 components against Architecture spec, Construction spec, structure_not_content.md, BUILD_DAY_ONE.md, BUILD_KORBAN.md, and heafar/book.md. Torah is the spec.
**Verdict:** All 6 components structurally sound for Phase 2. No golden calves. Three deviations found and corrected (2) or documented (1).
### Broken Tablets — What Broke and How It Was Fixed
**18. Zerikah fail-safe: empty offering kept instead of rejected**
- Problem: When no sentence qualified as vital, zerikah returned all sentences as vital content. BUILD_KORBAN.md §5 specifies: "the offering without vital organs is rejected, not processed into empty propositions."
- Root cause: Defensive fail-safe prioritized "don't lose data" over spec compliance.
- Fix: (1) zerikah.py now returns empty vital content when nothing is vital; all sentences go to discarded list. (2) korban.py checks for empty vital content after zerikah and returns a ProcessedKorban with empty propositions. (3) Tests updated: `test_empty_offering_rejected_not_processed`, `test_discarded_content_recorded`, `test_all_filler_rejected`.
- 181 tests pass.
- Required reading the specification again: Yes. BUILD_KORBAN.md §5 is explicit.
**19. Halt monitor not hardened per Architecture spec**
- Problem: halt_monitor.erl was a standard gen_server with no priority flags or exit trapping. Spec says: "Cannot be killed by other components. Cannot be bypassed. Highest priority."
- Fix: Added `process_flag(trap_exit, true)` and `process_flag(priority, max)` in init/1. Added 'EXIT' handler to survive linked process deaths. Supervisor already has `restart => permanent`.
- Lesson: The halt monitor is the last line of defense. If it can be killed, the three cardinal sins can go undetected.
**20. L1 lemma field stores Strong's numbers, not Hebrew dictionary form (KNOWN DEVIATION)**
- Problem: BUILD_DAY_ONE.md shows `"lemma": "רֵאשִׁית"` but build_l1.py stores `"lemma": "7225"` (Strong's concordance number).
- Status: Documented, not yet fixed. The morphhb XML uses Strong's numbers as lemma identifiers. Resolving these to Hebrew text requires a Strong's-to-Hebrew mapping that is not in the Construction spec's dependency list.
- Impact: Downstream consumers (Shoresh Engine) work around this by using the `shoresh` field instead. The lemma field is technically incorrect but does not block Phase 2 functionality.
- Resolution: Fix when connecting components (Step 14), or add Strong's lookup to L1 build dependencies.
### Design Notes
**What the code gets right (from review):**
- Avodah zarah firewall is compile-time (Prolog assertion: korban sources can never be valid authority)
- Te'amim reader hears the text's own structure (binary tree from melekh marks, not imposed parsing)
- Teiku is structural — both Talmudic Engine and Sanhedrin preserve unresolved tensions
- Shabbat is architecture, not feature toggle
- Korban Processor is fully stateless (kiyyor principle)
- Broken tablets carried in the aron (broken_tablets/ directory, this build log)
**Gap from the book (heafar):**
- The 13 Rules of Rabbi Yishmael are not implemented as Prolog inference predicates. The Talmudic Engine reasons ABOUT Torah but doesn't use Torah's own hermeneutic methods (kal va'chomer, gezerah shavah, binyan av, etc.). This is a significant conceptual gap for Phase 3.
- Cherubim architecture (voice from between facing intelligences) is partial — present in Sanhedrin deliberation but not in human-AI interaction layer.
+9
View File
@@ -53,6 +53,10 @@ check_vote(Vote) ->
%%==================================================================== %%====================================================================
init([]) -> init([]) ->
%% Architecture spec: halt monitor cannot be killed, cannot be bypassed,
%% highest priority. The halt monitor is the kohen gadol of process safety.
process_flag(trap_exit, true),
process_flag(priority, max),
{ok, #state{}}. {ok, #state{}}.
handle_call({check_case, Case}, _From, State) -> handle_call({check_case, Case}, _From, State) ->
@@ -71,6 +75,11 @@ handle_call(_Request, _From, State) ->
handle_cast(_Msg, State) -> handle_cast(_Msg, State) ->
{noreply, State}. {noreply, State}.
handle_info({'EXIT', _Pid, _Reason}, State) ->
%% Trap exit signals — halt monitor survives linked process deaths.
%% Architecture spec: "Cannot be killed by other components."
{noreply, State};
handle_info(_Info, State) -> handle_info(_Info, State) ->
{noreply, State}. {noreply, State}.
+109
View File
@@ -667,6 +667,106 @@ resolve(Case, Resolution) :-
%% If no resolution is possible %% If no resolution is possible
resolve(_, teiku). resolve(_, teiku).
%% === 13 RULES OF RABBI YISHMAEL (Hermeneutic Inference) ===
%%
%% The Torah's own interpretive methods. The engine must reason WITH
%% these rules, not merely reason about mitzvot derived from them.
%% Each middah is a Prolog inference predicate that derives new
%% propositions from existing Torah facts.
%% 1. Kal va'chomer — a fortiori (minor to major)
%% If a lenient case has restriction X, a stricter case certainly does.
kal_vachomer(Lenient, Strict, Property) :-
has_property(Lenient, Property),
stricter_than(Strict, Lenient).
%% 2. Gezerah shavah — verbal analogy
%% Shared language between two passages implies shared law.
gezerah_shavah(VerseA, VerseB, SharedLaw) :-
verse_contains_word(VerseA, Word),
verse_contains_word(VerseB, Word),
has_law(VerseA, SharedLaw).
%% 3. Binyan av mi'katuv echad — prototype from one verse
%% A law stated in one case applies to all similar cases.
binyan_av(SourceVerse, TargetCase, Law) :-
has_law(SourceVerse, Law),
similar_case(SourceVerse, TargetCase),
\+ explicitly_excluded(TargetCase, Law).
%% 4. Binyan av mi'shnei ketuvim — prototype from two verses
%% A law common to two cases applies to all similar cases.
binyan_av_two(VerseA, VerseB, TargetCase, Law) :-
has_law(VerseA, Law),
has_law(VerseB, Law),
similar_case(VerseA, TargetCase),
\+ explicitly_excluded(TargetCase, Law).
%% 5. Klal u'frat — general and particular
%% A general statement followed by a particular limits to the particular.
klal_ufrat(General, Particular, Scope) :-
follows(General, Particular),
Scope = limited_to(Particular).
%% 6. Frat u'klal — particular and general
%% A particular followed by a general expands to the general.
frat_uklal(Particular, General, Scope) :-
follows(Particular, General),
Scope = expanded_to(General).
%% 7. Klal u'frat u'klal — general, particular, general
%% Scope is determined by similarity to the particular.
klal_frat_klal(General1, Particular, General2, Scope) :-
follows(General1, Particular),
follows(Particular, General2),
Scope = similar_to(Particular).
%% 8. Klal she'hu tzarikh li'frat, u'frat she'hu tzarikh li'klal
%% General needing particular, particular needing general — mutual dependency.
klal_tzarikh_frat(General, Particular, Meaning) :-
requires_specification(General, Particular),
requires_generalization(Particular, General),
Meaning = mutual_definition(General, Particular).
%% 9. Davar she'hayah bi'khlal v'yatza min ha'klal l'lamed
%% Something included in a general rule that is singled out teaches
%% about the general rule, not only about itself.
davar_yatza_l_lamed(General, Exception, Teaching) :-
member_of(Exception, General),
singled_out(Exception, General),
Teaching = teaches_about(General, Exception).
%% 10. Davar she'hayah bi'khlal v'yatza lit'on to'an acher
%% Something singled out to teach a new matter — applies only to itself.
davar_yatza_toan_acher(General, Exception, NewMatter) :-
member_of(Exception, General),
singled_out_for(Exception, NewMatter),
applies_only_to(NewMatter, Exception).
%% 11. Davar she'hayah bi'khlal v'yatza la'don ba'davar he'chadash
%% Something singled out for a completely new category — cannot return
%% to the general rule unless Scripture explicitly returns it.
davar_yatza_chadash(General, Exception, NewCategory) :-
member_of(Exception, General),
new_category(Exception, NewCategory),
\+ explicitly_returned(Exception, General).
%% 12. Davar ha'lamed me'inyano, v'davar ha'lamed mi'sofo
%% Meaning derived from context, meaning derived from conclusion.
davar_lamed_inyan(Passage, ContextMeaning) :-
surrounding_context(Passage, Context),
implies(Context, ContextMeaning).
davar_lamed_sofo(Passage, ConclusionMeaning) :-
conclusion_of(Passage, Conclusion),
implies(Conclusion, ConclusionMeaning).
%% 13. Shnei ketuvim ha'makhchishim zeh et zeh
%% Two verses that contradict — resolved by a third verse.
shnei_ketuvim(VerseA, VerseB, VerseC, Resolution) :-
contradicts(VerseA, VerseB),
resolves_contradiction(VerseC, VerseA, VerseB),
Resolution = harmonized(VerseA, VerseB, VerseC).
%% === HALT CONDITIONS === %% === HALT CONDITIONS ===
%% Check for avodah zarah — misattributed authority %% Check for avodah zarah — misattributed authority
@@ -700,6 +800,15 @@ talmudic_engine/
│ │ ├── challenge.pl -- Kushya generation │ │ ├── challenge.pl -- Kushya generation
│ │ ├── resolve.pl -- Dialectical resolution │ │ ├── resolve.pl -- Dialectical resolution
│ │ └── teiku.pl -- Unresolved tension handling │ │ └── teiku.pl -- Unresolved tension handling
│ ├── middot/
│ │ ├── middot.pl -- 13 Rules of Rabbi Yishmael as inference predicates
│ │ ├── kal_vachomer.pl -- Rule 1: a fortiori (minor to major)
│ │ ├── gezerah_shavah.pl -- Rule 2: verbal analogy
│ │ ├── binyan_av.pl -- Rules 3-4: prototype from one/two verses
│ │ ├── klal_ufrat.pl -- Rules 5-8: general/particular interactions
│ │ ├── davar_yatza.pl -- Rules 9-11: exception teaching rules
│ │ ├── lamed_inyan.pl -- Rule 12: meaning from context/conclusion
│ │ └── shnei_ketuvim.pl -- Rule 13: contradicting verses resolved by third
│ ├── halt/ │ ├── halt/
│ │ ├── avodah_zarah.pl -- Idolatry detection │ │ ├── avodah_zarah.pl -- Idolatry detection
│ │ ├── shfichut_damim.pl -- Destruction detection │ │ ├── shfichut_damim.pl -- Destruction detection