From f8f70c77e50cbc2fa4a788b84b6eeaeb82ecfe9c Mon Sep 17 00:00:00 2001 From: Scott Karp Date: Mon, 9 Mar 2026 16:55:42 -0400 Subject: [PATCH] Code review fixes + 13 middot added to spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../src/korban_processor/korban.py | 12 ++ .../src/korban_processor/zerikah.py | 9 +- korban_processor/test/test_korban.py | 20 ++-- korban_processor/test/test_zerikah.py | 11 +- sanhedrin/src/halt_monitor.erl | 9 ++ specs/shorashim_architecture.md | 109 ++++++++++++++++++ 6 files changed, 150 insertions(+), 20 deletions(-) diff --git a/korban_processor/src/korban_processor/korban.py b/korban_processor/src/korban_processor/korban.py index daf3cbd0..c7d3fd5c 100644 --- a/korban_processor/src/korban_processor/korban.py +++ b/korban_processor/src/korban_processor/korban.py @@ -78,6 +78,18 @@ def process_korban( # 3. Zerikah — extract vital content 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 propositions = haktarah(vital) diff --git a/korban_processor/src/korban_processor/zerikah.py b/korban_processor/src/korban_processor/zerikah.py index 2e072d1e..b1afab02 100644 --- a/korban_processor/src/korban_processor/zerikah.py +++ b/korban_processor/src/korban_processor/zerikah.py @@ -96,11 +96,12 @@ def zerikah(terminated_content: str, korban: Korban) -> Tuple[str, List[str]]: else: discarded_sentences.append(sentence) - # If nothing was deemed vital, keep everything — - # better to pass through than to discard all content + # If nothing was deemed vital, the offering has no vital organs. + # 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: - vital_sentences = sentences - discarded_sentences = [] + discarded_sentences = sentences vital = " ".join(vital_sentences) return vital, discarded_sentences diff --git a/korban_processor/test/test_korban.py b/korban_processor/test/test_korban.py index d445735b..01c6f30c 100644 --- a/korban_processor/test/test_korban.py +++ b/korban_processor/test/test_korban.py @@ -266,21 +266,17 @@ class TestBuildVerification5_EmptyOffering: """System does not raise on empty offering — handles gracefully.""" 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. - Empty or minimal propositions are acceptable — garbage propositions are not. + The offering without vital organs is REJECTED — not processed + into empty propositions. BUILD_KORBAN.md §5. """ - for prop in self.result.propositions: - assert is_valid_prolog_term(prop), \ - f"Garbage proposition from empty offering: {prop!r}" + assert self.result.vital_content == "" + assert self.result.propositions == [] - def test_vital_content_has_no_assistant_voice(self): - """Whatever survives, it must not contain assistant preamble.""" - assert "Great question" not in self.result.vital_content - 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_discarded_content_recorded(self): + """What was stripped is recorded — the hide and offal are diagnostic data.""" + assert len(self.result.discarded) > 0 def test_source_query_preserved(self): assert self.result.source_query == "test empty" diff --git a/korban_processor/test/test_zerikah.py b/korban_processor/test/test_zerikah.py index 14a6cd43..51ced525 100644 --- a/korban_processor/test/test_zerikah.py +++ b/korban_processor/test/test_zerikah.py @@ -164,13 +164,16 @@ class TestZerikah: assert vital == "" assert discarded == [] - def test_all_filler_fallback_preserves_content(self): - """If nothing is vital, zerikah preserves everything rather than discarding all.""" + def test_all_filler_rejected(self): + """ + 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." korban = make_korban(filler) vital, discarded = zerikah(filler, korban) - # Since nothing qualifies as vital, fallback returns everything - assert vital != "" or discarded == [] + assert vital == "" + assert len(discarded) > 0 def test_stateless_between_calls(self): """Kiyyor principle: second call unaffected by first.""" diff --git a/sanhedrin/src/halt_monitor.erl b/sanhedrin/src/halt_monitor.erl index 1add5d01..2ced654a 100644 --- a/sanhedrin/src/halt_monitor.erl +++ b/sanhedrin/src/halt_monitor.erl @@ -53,6 +53,10 @@ check_vote(Vote) -> %%==================================================================== 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{}}. handle_call({check_case, Case}, _From, State) -> @@ -71,6 +75,11 @@ handle_call(_Request, _From, State) -> handle_cast(_Msg, 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) -> {noreply, State}. diff --git a/specs/shorashim_architecture.md b/specs/shorashim_architecture.md index 86b3c24b..3ed855be 100644 --- a/specs/shorashim_architecture.md +++ b/specs/shorashim_architecture.md @@ -667,6 +667,106 @@ resolve(Case, Resolution) :- %% If no resolution is possible 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 === %% Check for avodah zarah — misattributed authority @@ -700,6 +800,15 @@ talmudic_engine/ │ │ ├── challenge.pl -- Kushya generation │ │ ├── resolve.pl -- Dialectical resolution │ │ └── 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/ │ │ ├── avodah_zarah.pl -- Idolatry detection │ │ ├── shfichut_damim.pl -- Destruction detection