Phase 3: Intelligence — 613 mitzvot, 13 middot, Tzara'at diagnostic

Encode all 613 mitzvot from Rambam's Sefer HaMitzvot (248 positive,
365 prohibitions). Build the 13 Middot of Rabbi Yishmael as Prolog
inference predicates for hermeneutical reasoning. Implement Tzara'at
multi-stage input validation following Vayikra 13-14. Enhance kushya
generation with kal vachomer and shnei ketuvim challenge strategies.

Architecture §4 Steps 14-15, 19-20.

New files:
  middot/   — 8 files (master + 7 rule modules)
  diagnostic/ — 4 files (tzaraat, inspection, quarantine, diagnosis)

Modified:
  mitzvot.pl     — 52 → 613 mitzvot
  relationships.pl — expanded tensions, complements, action domains
  challenge.pl   — middot-based kushya (kal vachomer, shnei ketuvim)

Tests: 293 total (184 Prolog, 76 Elixir, 33 Haskell), 0 failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott KarpandClaude Opus 4.6 committed 2026-03-13 16:18:58 -04:00
1 parent ae78ed26d1
commit 52837cee18
19 files changed
+2795 -178

No files matched your search

+353
View File
@@ -0,0 +1,353 @@
%% middot_test.pl — Tests for the 13 Middot of Rabbi Yishmael.
%%
%% Phase 3: Hermeneutical inference predicates.
:- use_module(library(plunit)).
:- use_module('../src/middot/middot').
:- use_module('../src/middot/kal_vachomer').
:- use_module('../src/middot/gezerah_shavah').
:- use_module('../src/middot/binyan_av').
:- use_module('../src/middot/klal_ufrat').
:- use_module('../src/middot/davar_yatza').
:- use_module('../src/middot/lamed_inyan').
:- use_module('../src/middot/shnei_ketuvim').
:- use_module('../src/specification/mitzvot').
:- use_module('../src/specification/torah_text').
:- use_module('../src/specification/root_meanings').
%% ============================================================
%% MASTER MODULE TESTS
%% ============================================================
:- begin_tests(middot_master).
%% All 13 middot are named
test(all_thirteen_named) :-
findall(N, middah_name(N, _), Numbers),
length(Numbers, 13).
%% all_middot returns a list of 13 names
test(all_middot_list) :-
all_middot(Middot),
length(Middot, 13).
%% Key middot names are correct
test(middah_1_is_kal_vachomer) :-
middah_name(1, kal_vachomer).
test(middah_2_is_gezerah_shavah) :-
middah_name(2, gezerah_shavah).
test(middah_13_is_shnei_ketuvim) :-
middah_name(13, shnei_ketuvim).
%% apply_middah works for rule 1 (kal vachomer)
test(apply_middah_1, [nondet]) :-
apply_middah(1, shabbat_rest, festival_rest, _Reasoning).
%% apply_middah works for rule 5 (klal ufrat)
test(apply_middah_5, [nondet]) :-
apply_middah(5,
general(fruit_trees),
particular([olive, grape]),
narrowed_to([olive, grape], from(fruit_trees))).
:- end_tests(middot_master).
%% ============================================================
%% RULE 1: KAL VACHOMER
%% ============================================================
:- begin_tests(kal_vachomer_tests).
%% Severity ordering: monetary < physical < capital < divine
test(monetary_less_than_physical) :-
severity_ordering(monetary, physical).
test(physical_less_than_capital) :-
severity_ordering(physical, capital).
test(capital_less_than_divine) :-
severity_ordering(capital, divine).
%% Transitive stringency
test(monetary_less_than_capital) :-
is_more_stringent(monetary, capital).
test(monetary_less_than_divine) :-
is_more_stringent(monetary, divine).
%% Kal vachomer: shabbat → festival
test(shabbat_to_festival) :-
kal_vachomer(shabbat_rest, festival_rest, _, definite).
%% Kal vachomer: monetary → physical compensation
test(monetary_to_physical) :-
kal_vachomer(compensate_monetary, compensate_physical, _, definite).
%% Kal vachomer: domain-based derivation
test(domain_derivation, [nondet]) :-
kal_vachomer(
law_in(monetary, _),
law_in(physical, _),
derived(_, from(monetary), to(physical), kal_vachomer),
definite
).
%% Unconditional mitzvah implies conditional
test(unconditional_implies_conditional, [nondet]) :-
kal_vachomer(
mitzvah_applies(1, always),
mitzvah_applies(1, when(_)),
derived(1, unconditional_implies_conditional, kal_vachomer),
definite
).
%% Dayo principle: derived law equals source law
test(dayo_principle) :-
kal_vachomer:dayo(derived(some_law, _, _, _), some_law).
:- end_tests(kal_vachomer_tests).
%% ============================================================
%% RULE 2: GEZERAH SHAVAH
%% ============================================================
:- begin_tests(gezerah_shavah_tests).
%% Known gezerah shavah: shabbat ↔ pesach (shemirah)
test(shabbat_pesach_shemirah) :-
gezerah_shavah(shabbat_keeping, pesach_keeping, _, shin_mem_resh).
%% Known gezerah shavah: shabbat ↔ circumcision (ot)
test(shabbat_circumcision_ot) :-
gezerah_shavah(shabbat_sign, circumcision_sign, _, aleph_vav_tav).
%% Shared keyword: two verses sharing a root link
test(shared_keyword_creation, [nondet]) :-
shared_keyword(ref(bereishit, 1, 1), ref(bereishit, 1, 27), bet_resh_aleph).
%% Dynamic gezerah shavah from Torah text
test(dynamic_gezerah_shavah, [nondet]) :-
gezerah_shavah(ref(bereishit, 1, 1), ref(bereishit, 1, 27), _, bet_resh_aleph).
:- end_tests(gezerah_shavah_tests).
%% ============================================================
%% RULES 3-4: BINYAN AV
%% ============================================================
:- begin_tests(binyan_av_tests).
%% Binyan av from one verse: source verse establishes domain law
%% Using Shemot 20:8 — mitzvah 154 (rest_on_shabbat), root qof_dalet_shin → holiness
test(binyan_av_one_verse, [nondet]) :-
binyan_av_one(ref(shemot, 20, 8), applies_to_domain(_, _), _).
%% Binyan av from two verses: common root found
test(binyan_av_two_verses, [nondet]) :-
binyan_av_two(
ref(bereishit, 1, 1),
ref(bereishit, 1, 27),
general_rule(bet_resh_aleph, creation),
bet_resh_aleph
).
%% Unified interface: single verse
test(binyan_av_single, [nondet]) :-
binyan_av(single_verse(ref(shemot, 20, 8)), _, _).
%% Unified interface: two verses
test(binyan_av_two_unified, [nondet]) :-
binyan_av(
two_verses(ref(bereishit, 1, 1), ref(bereishit, 1, 27)),
_,
_
).
%% Common principle extraction
test(common_principle_from_list, [nondet]) :-
common_principle(
[ref(bereishit, 1, 1), ref(bereishit, 1, 27)],
bet_resh_aleph,
_
).
:- end_tests(binyan_av_tests).
%% ============================================================
%% RULES 5-8: KLAL UFRAT
%% ============================================================
:- begin_tests(klal_ufrat_tests).
%% Rule 5: General then particular narrows
test(klal_ufrat_narrows) :-
klal_ufrat(
general(fruit_trees),
particular([olive, grape]),
narrowed_to([olive, grape], from(fruit_trees))
).
%% Rule 6: Particular then general expands
test(prat_uklal_expands) :-
prat_uklal(
particular([sheep, cattle]),
general(all_livestock),
expanded_to(all_livestock, including([sheep, cattle]))
).
%% Rule 7: General-particular-general yields similar
test(klal_ufrat_uklal_similar) :-
klal_ufrat_uklal(
general(all_damage),
particular([ox_damage, pit_damage, fire_damage]),
general(all_damage),
similar_to([ox_damage, pit_damage, fire_damage],
within(all_damage, negligence_based_damage))
).
%% Generic klal ufrat
test(generic_klal_ufrat) :-
klal_ufrat(
general(any_category),
particular(specific_thing),
narrowed_to(specific_thing, from(any_category))
).
%% Scope results
test(scope_narrowed) :-
scope_result(klal_then_prat, narrowed).
test(scope_expanded) :-
scope_result(prat_then_klal, expanded).
test(scope_similar) :-
scope_result(klal_prat_klal, similar).
:- end_tests(klal_ufrat_tests).
%% ============================================================
%% RULES 9-11: DAVAR YATZA
%% ============================================================
:- begin_tests(davar_yatza_tests).
%% Rule 9: Exception teaches about the general
test(exception_teaches_general) :-
davar_yatza_llamed(
exception(chatat, blood_sprinkling_north),
general(all_offerings),
teaches(blood_sprinkling_north, applies_to_all(all_offerings))
).
%% Generic rule 9
test(generic_exception_teaches) :-
davar_yatza_llamed(
exception(item, some_law),
general(category),
teaches(some_law, applies_to_all(category))
).
%% Rule 10: Same type reduces severity
test(same_type_reduces) :-
davar_yatza_toan_echad(
exception(item, shared_law),
general(category, shared_law),
reduces(shared_law, for(category), because(item))
).
%% Rule 11: Different type creates dual application
test(different_type_dual_rules) :-
davar_yatza_toan_acher(
exception(metzora, outside_camp_procedure),
general(tumah, standard_purification),
dual_rules(metzora, follows(standard_purification), and(outside_camp_procedure))
).
:- end_tests(davar_yatza_tests).
%% ============================================================
%% RULE 12: LAMED INYAN
%% ============================================================
:- begin_tests(lamed_inyan_tests).
%% "Lo tignov" in context of capital crimes = kidnapping
test(lo_tignov_kidnapping) :-
lamed_me_inyano(
ref(shemot, 20, 15),
context(capital_crimes_sequence),
meaning(lo_tignov, refers_to(kidnapping), not(ordinary_theft))
).
%% Context from surrounding verses (requires prev and next verse to exist)
test(context_from_surrounding, [nondet]) :-
lamed_me_inyano(
ref(shemot, 20, 14),
context(ref(shemot, 20, 13), ref(shemot, 20, 15)),
meaning_from(context, ref(shemot, 20, 14))
).
%% Lamed misofo: conclusion clarifies ambiguous topic
test(conclusion_clarifies) :-
lamed_misofo(
ambiguous(topic),
concludes_with(clarification),
meaning(topic, clarified_by(clarification))
).
%% Unified context_determines_meaning
test(context_determines_surrounding, [nondet]) :-
context_determines_meaning(
ref(shemot, 20, 15),
surrounding,
meaning(lo_tignov, refers_to(kidnapping), not(ordinary_theft))
).
:- end_tests(lamed_inyan_tests).
%% ============================================================
%% RULE 13: SHNEI KETUVIM
%% ============================================================
:- begin_tests(shnei_ketuvim_tests).
%% Domains in tension
test(judgment_mercy_tension) :-
domains_in_tension(judgment, mercy).
test(mercy_judgment_tension) :-
domains_in_tension(mercy, judgment).
test(holiness_transgression_tension) :-
domains_in_tension(holiness, transgression).
%% Two verses with roots in opposing domains contradict
test(verses_contradict_detection, [nondet]) :-
verses_contradict(V1, V2, domain_conflict(_, _)),
V1 \= V2.
%% Resolving verse shares roots with both
test(resolving_verse_found, [nondet]) :-
resolving_verse(V1, V2, V3),
V1 \= V3,
V2 \= V3.
%% Shnei ketuvim unified interface — harmonized case
test(shnei_ketuvim_harmonized, [nondet]) :-
shnei_ketuvim(
contradicting(V1, V2),
harmonized_reading(V1, V2, _V3),
reasoning(V1, contradicts(V2), resolved_by(_)),
_
).
:- end_tests(shnei_ketuvim_tests).
+85 -19
View File
@@ -1,4 +1,7 @@
%% mitzvot_test.pl — Tests for the specification layer.
%%
%% Phase 3: Updated for full 613 mitzvot encoding.
%% Numbering: Positive 1-248, Prohibition 249-613.
:- use_module(library(plunit)).
:- use_module('../src/specification/mitzvot').
@@ -8,41 +11,62 @@
:- begin_tests(mitzvot).
%% The specification has mitzvot
test(mitzvot_exist) :-
total_encoded(Count),
Count > 0.
%% The specification has exactly 613 mitzvot
test(total_613) :-
total_encoded(613).
%% The three categories exist
%% The two categories exist
test(positive_mitzvot_exist) :-
mitzvah(_, positive, _, _).
test(prohibition_mitzvot_exist) :-
mitzvah(_, prohibition, _, _).
test(belief_mitzvot_exist) :-
mitzvah(_, belief, _, _).
%% Category counts match Rambam
test(positive_count) :-
mitzvah_category_count(positive, 248).
%% The first mitzvah is know_hashem
test(prohibition_count) :-
mitzvah_category_count(prohibition, 365).
%% The first mitzvah is know_hashem (Rambam Positive #1)
test(first_mitzvah) :-
mitzvah(1, belief, know_hashem, always).
mitzvah(1, positive, know_hashem, always).
%% Core beliefs
test(unity_of_hashem) :-
mitzvah(2, positive, unity_of_hashem, always).
test(love_hashem) :-
mitzvah(3, positive, love_hashem, always).
test(fear_hashem) :-
mitzvah(4, positive, fear_hashem, always).
%% Shabbat mitzvot exist
test(shabbat_rest) :-
mitzvah(154, positive, rest_on_shabbat, when(seventh_day)).
test(shabbat_no_work) :-
mitzvah(155, prohibition, not_work_on_shabbat, when(seventh_day)).
test(sanctify_shabbat) :-
mitzvah(155, positive, sanctify_shabbat, when(seventh_day)).
%% The three halt conditions have their mitzvot
%% (Prohibitions are numbered 249-613)
test(no_other_gods) :-
mitzvah(2, prohibition, no_other_gods, always).
mitzvah(249, prohibition, no_other_gods, always).
test(no_murder) :-
mitzvah(289, prohibition, not_murder, always).
test(not_murder) :-
mitzvah(461, prohibition, not_murder, always).
test(no_adultery) :-
mitzvah(330, prohibition, not_commit_adultery, always).
test(not_commit_adultery) :-
mitzvah(491, prohibition, not_commit_adultery, always).
%% Additional halt-related
test(not_make_idols) :-
mitzvah(250, prohibition, not_make_idols, always).
test(maintain_boundaries) :-
mitzvah(532, prohibition, maintain_boundaries, always).
%% Source references exist
test(mitzvah_sources_exist) :-
@@ -51,6 +75,48 @@ test(mitzvah_sources_exist) :-
test(shabbat_source) :-
mitzvah_source(154, ref(shemot, 20, 8)).
test(murder_source) :-
mitzvah_source(461, ref(shemot, 20, 13)).
%% Korbanot
test(olah_exists) :-
mitzvah(64, positive, bring_olah, when(offering_required)).
test(chatat_exists) :-
mitzvah(65, positive, bring_chatat, when(unintentional_sin)).
%% Agriculture
test(bikurim_exists) :-
mitzvah(125, positive, bring_bikurim, when(first_fruits_available)).
test(shmitah_exists) :-
mitzvah(122, positive, let_land_rest_shmitah, when(seventh_year)).
%% Justice
test(appoint_judges) :-
mitzvah(165, positive, appoint_judges, always).
test(not_accept_bribe) :-
mitzvah(440, prohibition, judge_not_accept_bribe, always).
%% Interpersonal
test(love_fellow) :-
mitzvah(192, positive, love_fellow_jew, always).
test(not_gossip) :-
mitzvah(472, prohibition, not_gossip, always).
%% Kashrut
test(not_eat_blood) :-
mitzvah(561, prohibition, not_eat_blood, always).
test(not_eat_meat_milk) :-
mitzvah(564, prohibition, not_eat_meat_and_milk, always).
%% Last mitzvah
test(last_mitzvah) :-
mitzvah(613, prohibition, not_eat_sanctified_that_became_tamei, when(offering)).
:- end_tests(mitzvot).
@@ -105,13 +171,13 @@ test(kadash_gematria) :-
:- begin_tests(relationships).
%% Tensions exist
%% Tensions exist (updated IDs for Phase 3 numbering)
test(justice_mercy_tension) :-
mitzvah_conflicts_with(179, 295, _).
mitzvah_conflicts_with(178, 489, _).
%% Complementary mitzvot
test(complementary) :-
complementary_mitzvot(1, 3).
complementary_mitzvot(1, 2).
%% Root consonant sharing
test(consonant_sharing) :-
+213
View File
@@ -0,0 +1,213 @@
%% phase3_verify_test.pl — Phase 3 verification tests.
%%
%% Architecture §4, Step 20: Verify the system can:
%% 1. Deliberate a case through full dialectical process
%% 2. Produce majority decision with preserved dissent
%% 3. Apply middot-based reasoning
%% 4. Run diagnostic protocol
%% 5. Query 613 mitzvot by domain, condition, category
:- use_module(library(plunit)).
:- use_module('../src/reasoning/interpret').
:- use_module('../src/reasoning/challenge').
:- use_module('../src/reasoning/resolve').
:- use_module('../src/reasoning/teiku').
:- use_module('../src/specification/mitzvot').
:- use_module('../src/specification/torah_text').
:- use_module('../src/specification/relationships').
:- use_module('../src/specification/root_meanings').
:- use_module('../src/middot/middot').
:- use_module('../src/middot/kal_vachomer').
:- use_module('../src/middot/gezerah_shavah').
:- use_module('../src/middot/shnei_ketuvim').
:- use_module('../src/diagnostic/tzaraat').
:- use_module('../src/diagnostic/inspection').
:- use_module('../src/diagnostic/diagnosis').
%% ============================================================
%% STEP 20: FULL DELIBERATION CYCLE
%% ============================================================
:- begin_tests(full_deliberation).
%% A case about holiness can be interpreted
test(interpret_holiness, [nondet]) :-
Case = case(verify_holiness, holiness, [qof_dalet_shin],
'What does it mean to be holy?'),
interpret(Case, interpretation(verify_holiness, _, _, _, _)).
%% A case about judgment produces a challenge
test(judgment_case_challenged, [nondet]) :-
Case = case(verify_judgment, judgment, [shin_pe_tet, tzade_dalet_qof],
'How to judge righteously?'),
interpret(Case, Interpretation),
challenge(Interpretation, kushya(_, _, _)).
%% A case can go through full resolution
test(full_resolve_cycle, [nondet]) :-
Case = case(verify_resolve, holiness, [qof_dalet_shin],
'What is the holiness obligation?'),
resolve(Case, Resolution),
( Resolution = accepted(_)
; Resolution = challenged(_, _)
; Resolution = synthesized(_, _, _)
; Resolution = teiku(_)
).
%% Teiku count is tracked
test(teiku_tracking) :-
teiku_count(Count),
integer(Count).
:- end_tests(full_deliberation).
%% ============================================================
%% STEP 20: 613 MITZVOT QUERYABILITY
%% ============================================================
:- begin_tests(mitzvot_queryability).
%% Query all mitzvot in a domain
test(query_by_domain, [nondet]) :-
domain_mitzvot(judgment, Mitzvot),
Mitzvot \= [].
%% Query mitzvot by category
test(query_positives) :-
findall(ID, mitzvah(ID, positive, _, _), IDs),
length(IDs, 248).
test(query_prohibitions) :-
findall(ID, mitzvah(ID, prohibition, _, _), IDs),
length(IDs, 365).
%% Query conditional mitzvot
test(query_conditional, [nondet]) :-
mitzvah(_, _, _, when(_)).
%% Query unconditional mitzvot
test(query_unconditional, [nondet]) :-
mitzvah(_, _, _, always).
%% Mitzvah source grounding
test(sources_exist) :-
findall(ID, mitzvah_source(ID, _), IDs),
length(IDs, Count),
Count > 50. %% At least 50 mitzvot have sources
%% Complementary pairs exist
test(complementary_pairs) :-
findall(pair(A,B), complementary_mitzvot(A, B), Pairs),
length(Pairs, Count),
Count >= 16. %% 8 pairs * 2 directions
%% Tension pairs exist
test(tension_pairs) :-
findall(pair(A,B), mitzvah_conflicts_with(A, B, _), Pairs),
length(Pairs, Count),
Count >= 8.
:- end_tests(mitzvot_queryability).
%% ============================================================
%% STEP 20: MIDDOT APPLICATION
%% ============================================================
:- begin_tests(middot_application).
%% All 13 middot are enumerated
test(all_13_enumerated) :-
all_middot(M),
length(M, 13).
%% Kal vachomer produces derivation
test(kal_vachomer_derivation) :-
kal_vachomer(shabbat_rest, festival_rest, Derived, definite),
Derived = derived(rest_obligation, from(shabbat), to(festival), kal_vachomer).
%% Gezerah shavah finds verbal link
test(gezerah_shavah_link) :-
gezerah_shavah(shabbat_keeping, pesach_keeping, _, shin_mem_resh).
%% Gezerah shavah from Torah text
test(dynamic_verbal_link, [nondet]) :-
shared_keyword(ref(bereishit, 1, 1), ref(bereishit, 1, 27), bet_resh_aleph).
%% Shnei ketuvim: domain tensions known
test(domain_tensions_known) :-
domains_in_tension(judgment, mercy),
domains_in_tension(holiness, transgression).
%% Challenge module has middot-based strategies
test(challenge_has_kal_vachomer_strategy) :-
challenge_strength(contradiction(kal_vachomer_tension, _, _, _), strong).
test(challenge_has_shnei_ketuvim_strategy) :-
challenge_strength(contradiction(shnei_ketuvim_tension, _, _, _), strong).
:- end_tests(middot_application).
%% ============================================================
%% STEP 20: TZARA'AT DIAGNOSTIC
%% ============================================================
:- begin_tests(tzaraat_diagnostic).
%% Clean input passes
test(clean_input_passes) :-
diagnose(input(declared, [visible], within_bounds, []), declared_tahor).
%% Impure input rejected
test(impure_input_rejected) :-
diagnose(input(undeclared, [visible], within_bounds, []), confirmed_tamei).
%% Ambiguous input quarantined
test(ambiguous_input_quarantined) :-
diagnose(input(declared, [hidden], within_bounds, []), requires_quarantine(quarantine_1)).
%% The paradox: full visibility = purity
test(full_visibility_paradox) :-
diagnose(
input(undeclared, [hidden], exceeds_bounds, [leaks_implementation]),
fully_visible_tahor
).
%% 8 diagnosis states
test(eight_states) :-
findall(S, diagnosis_state(S), States),
length(States, 8).
:- end_tests(tzaraat_diagnostic).
%% ============================================================
%% STEP 20: ROOT AND VERSE QUERYABILITY
%% ============================================================
:- begin_tests(root_verse_queries).
%% All semantic domains
test(domains_exist) :-
all_domains(Domains),
length(Domains, Count),
Count >= 10.
%% Roots in a domain
test(roots_in_holiness, [nondet]) :-
roots_in_domain(holiness, Roots),
Roots \= [].
%% Verses mentioning a root
test(verses_with_kadosh, [nondet]) :-
verses_mentioning_root(qof_dalet_shin, Verses),
Verses \= [].
%% Verse roots can be retrieved
test(verse_roots_retrieved) :-
verse_roots(bereishit, 1, 1, Roots),
member(bet_resh_aleph, Roots).
:- end_tests(root_verse_queries).
+202
View File
@@ -0,0 +1,202 @@
%% tzaraat_test.pl — Tests for the Tzara'at diagnostic protocol.
%%
%% Phase 3, Step 19: Multi-stage input validation.
:- use_module(library(plunit)).
:- use_module('../src/diagnostic/tzaraat').
:- use_module('../src/diagnostic/diagnosis').
:- use_module('../src/diagnostic/inspection').
:- use_module('../src/diagnostic/quarantine').
%% ============================================================
%% DIAGNOSIS STATE TESTS
%% ============================================================
:- begin_tests(diagnosis_states).
%% All 8 states exist
test(all_states_exist) :-
findall(S, diagnosis_state(S), States),
length(States, 8).
%% Terminal states
test(tamei_is_terminal) :-
terminal_state(confirmed_tamei).
test(tahor_is_terminal) :-
terminal_state(declared_tahor).
test(fully_visible_is_terminal) :-
terminal_state(fully_visible_tahor).
%% Initial state
test(initial_state_is_inspection) :-
initial_state(initial_inspection).
%% Valid transitions
test(initial_to_quarantine) :-
valid_transition(initial_inspection, quarantine_1).
test(initial_to_tamei) :-
valid_transition(initial_inspection, confirmed_tamei).
test(initial_to_tahor) :-
valid_transition(initial_inspection, declared_tahor).
test(initial_to_fully_visible) :-
valid_transition(initial_inspection, fully_visible_tahor).
test(quarantine_1_to_reinspection) :-
valid_transition(quarantine_1, reinspection_1).
test(quarantine_2_to_final_reinspection) :-
valid_transition(quarantine_2, reinspection_2).
%% Transition reasons exist
test(transition_has_reason) :-
transition_reason(initial_inspection, quarantine_1, Reason),
Reason \= ''.
:- end_tests(diagnosis_states).
%% ============================================================
%% INSPECTION TESTS
%% ============================================================
:- begin_tests(inspection_tests).
%% Clean input → declared tahor
test(clean_input_tahor) :-
Input = input(declared, [visible, visible], within_bounds, []),
inspect(Input, declared_tahor).
%% Undeclared source → confirmed tamei (high severity)
test(undeclared_source_tamei) :-
Input = input(undeclared, [visible], within_bounds, []),
inspect(Input, confirmed_tamei).
%% Hidden dependency only → quarantine (moderate severity)
test(hidden_dep_quarantine) :-
Input = input(declared, [hidden], within_bounds, []),
inspect(Input, quarantine_1).
%% Scope creep → quarantine (moderate severity)
test(scope_creep_quarantine) :-
Input = input(declared, [visible], exceeds_bounds, []),
inspect(Input, quarantine_1).
%% The paradox: ALL signs present → fully visible tahor
test(fully_visible_paradox) :-
Input = input(undeclared, [hidden], exceeds_bounds, [leaks_implementation]),
inspect(Input, fully_visible_tahor).
%% Low severity only → declared tahor
test(low_severity_tahor) :-
Input = input(declared, [visible], within_bounds, [leaks_implementation]),
inspect(Input, declared_tahor).
%% Sign detection
test(detect_undeclared_source) :-
has_sign(input(undeclared, [], within_bounds, []), undeclared_source).
test(detect_hidden_dependency) :-
has_sign(input(declared, [hidden], within_bounds, []), hidden_dependency).
test(detect_scope_creep) :-
has_sign(input(declared, [], exceeds_bounds, []), scope_creep).
test(detect_exposed_internals) :-
has_sign(input(declared, [], within_bounds, [leaks_implementation]), exposed_internals).
%% Sign count
test(clean_has_zero_signs) :-
count_signs(input(declared, [visible], within_bounds, []), 0).
test(all_signs_count_four) :-
count_signs(input(undeclared, [hidden], exceeds_bounds, [leaks_implementation]), 4).
:- end_tests(inspection_tests).
%% ============================================================
%% QUARANTINE TESTS
%% ============================================================
:- begin_tests(quarantine_tests).
%% Can quarantine an input
test(quarantine_creates_record,
[cleanup(retractall(quarantine:quarantine_record(_, _, _, _)))]) :-
retractall(quarantine:quarantine_record(_, _, _, _)),
Input = input(declared, [hidden], within_bounds, []),
quarantine_input(test_q1, Input, quarantine_1),
quarantine:quarantine_record(test_q1, quarantine_1, Signs, 1),
member(hidden_dependency, Signs).
%% Reinspection: signs cleared → tahor
test(reinspect_cleared_tahor,
[cleanup(retractall(quarantine:quarantine_record(_, _, _, _)))]) :-
retractall(quarantine:quarantine_record(_, _, _, _)),
Input1 = input(declared, [hidden], within_bounds, []),
quarantine_input(test_r1, Input1, quarantine_1),
%% After quarantine, input is clean
Input2 = input(declared, [visible], within_bounds, []),
reinspect(test_r1, Input2, declared_tahor).
%% Reinspection: signs spread → tamei
test(reinspect_spread_tamei,
[cleanup(retractall(quarantine:quarantine_record(_, _, _, _)))]) :-
retractall(quarantine:quarantine_record(_, _, _, _)),
Input1 = input(declared, [hidden], within_bounds, []),
quarantine_input(test_r2, Input1, quarantine_1),
%% After quarantine, more problems appeared
Input2 = input(declared, [hidden], exceeds_bounds, [leaks_implementation]),
reinspect(test_r2, Input2, confirmed_tamei).
:- end_tests(quarantine_tests).
%% ============================================================
%% TZARA'AT MASTER MODULE TESTS
%% ============================================================
:- begin_tests(tzaraat_master).
%% Clean input diagnosed immediately
test(diagnose_clean) :-
Input = input(declared, [visible], within_bounds, []),
diagnose(Input, declared_tahor).
%% Impure input diagnosed immediately
test(diagnose_impure) :-
Input = input(undeclared, [visible], within_bounds, []),
diagnose(Input, confirmed_tamei).
%% Ambiguous input requires quarantine
test(diagnose_ambiguous) :-
Input = input(declared, [hidden], within_bounds, []),
diagnose(Input, requires_quarantine(quarantine_1)).
%% Full diagnostic flow: clean input
test(full_flow_clean) :-
Input = input(declared, [visible], within_bounds, []),
full_diagnostic_flow(flow_clean, [Input], declared_tahor).
%% Full diagnostic flow: immediate tamei
test(full_flow_tamei) :-
Input = input(undeclared, [visible], within_bounds, []),
full_diagnostic_flow(flow_tamei, [Input], confirmed_tamei).
%% Full diagnostic flow: fully visible paradox
test(full_flow_paradox) :-
Input = input(undeclared, [hidden], exceeds_bounds, [leaks_implementation]),
full_diagnostic_flow(flow_paradox, [Input], fully_visible_tahor).
%% Full diagnostic flow: quarantine then cleared
test(full_flow_quarantine_cleared, [cleanup(retractall(quarantine_record(_, _, _, _)))]) :-
Input1 = input(declared, [hidden], within_bounds, []),
Input2 = input(declared, [visible], within_bounds, []),
full_diagnostic_flow(flow_q_clear, [Input1, Input2], declared_tahor).
:- end_tests(tzaraat_master).