Step 14: Connect components — chamishim karsay zahav

Three connections built and tested:
- Haskell Shoresh Engine via Erlang Port (JSON lines over stdin/stdout)
- Prolog Talmudic Engine via Unix domain socket (JSON over /tmp/shorashim/talmudic.sock)
- Erlang Sanhedrin via native BEAM calls

New: Server.hs (JSON-line server loop), json_bridge.pl + socket_server.pl
(Prolog socket interface), ShoreshEnginePort + TalmudicEngineSocket +
SanhedrinClient (Elixir GenServer wrappers). BereishitCycle now routes
unresolved cases through Talmudic Engine and Sanhedrin. All connections
gracefully degrade — pipeline falls back to L1 data when engines unavailable.

46 Elixir tests, 33 Haskell tests, 57 Prolog tests — 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-12 17:39:21 -04:00
1 parent f8f70c77e5
commit c67672777b
18 files changed
+1435 -121

No files matched your search

@@ -0,0 +1,194 @@
%% json_bridge.pl — JSON ↔ Prolog term conversion for Talmudic Engine.
%%
%% Construction §3.3: Bidirectional conversion between the JSON schema
%% defined in the spec and internal Prolog terms used by api.pl.
%%
%% Inbound: JSON dict → Prolog request term
%% Outbound: Prolog response term → JSON dict
:- module(json_bridge, [
json_to_request/2,
response_to_json/2,
parse_root_name/2
]).
:- use_module(library(json)).
:- use_module(library(lists)).
%% ============================================================
%% INBOUND: JSON → Prolog request
%% ============================================================
%% Convert a JSON dict (as parsed by SWI-Prolog's json library) to a
%% Prolog request term compatible with api:process_request/2.
%%
%% JSON format (Construction §3.3):
%% { "action": "interpret", "case": { "ref": "case-uuid",
%% "facts": [...], "question": "...", "context_roots": [...] } }
%%
%% Prolog format:
%% request(interpret, CaseRef, Domain, Roots, Question)
json_to_request(Dict, request(Action, CaseRef, Domain, Roots, Question)) :-
get_dict(action, Dict, ActionStr),
atom_string(Action, ActionStr),
get_dict(case, Dict, CaseDict),
get_dict(ref, CaseDict, RefStr),
atom_string(CaseRef, RefStr),
%% Extract question
( get_dict(question, CaseDict, QuestionStr)
-> atom_string(Question, QuestionStr)
; Question = none
),
%% Extract context_roots and derive domain
( get_dict(context_roots, CaseDict, RootNames)
-> maplist(parse_root_name, RootNames, Roots)
; Roots = []
),
%% Derive domain from roots (first root's known domain, or 'general')
( Roots = [FirstRoot|_],
root_to_domain(FirstRoot, D)
-> Domain = D
; Domain = general
).
%% Handle halt_check action (different format — no case, just action)
json_to_request(Dict, request(halt_check, Action)) :-
get_dict(action, Dict, "halt_check"),
get_dict(check, Dict, CheckDict),
get_dict(type, CheckDict, TypeStr),
atom_string(Type, TypeStr),
( get_dict(target, CheckDict, TargetStr)
-> atom_string(Target, TargetStr)
; Target = unknown
),
( get_dict(source, CheckDict, SourceStr)
-> atom_string(Source, SourceStr)
; Source = unknown
),
Action = action(Type, source(Source), target(Target)).
%% ============================================================
%% OUTBOUND: Prolog response → JSON
%% ============================================================
%% Convert a Prolog response from api.pl to a JSON-serializable dict.
%%
%% Prolog format: response(ok, interpreted, result(...))
%% JSON format (Construction §3.3):
%% { "status": "resolved", "interpretation": { ... } }
%% Interpreted response
response_to_json(response(ok, interpreted, result(ref(Ref), mitzvot(Mitzvot),
verses(Verses), reading(Reading),
confidence(Conf))),
Dict) :-
maplist(term_to_string, Mitzvot, MitzvotStrs),
maplist(term_to_string, Verses, VerseStrs),
term_to_string(Reading, ReadingStr),
atom_string(Conf, ConfStr),
atom_string(Ref, RefStr),
Dict = json{status: "resolved",
interpretation: json{text: ReadingStr,
prolog_form: RefStr,
grounding: MitzvotStrs,
verses: VerseStrs,
confidence: ConfStr},
challenge: null}.
%% Challenged response
response_to_json(response(ok, resolved_challenged,
result(interpretation(Interp), challenge(Kushya))),
Dict) :-
term_to_string(Interp, InterpStr),
term_to_string(Kushya, KushyaStr),
Dict = json{status: "challenged",
interpretation: json{text: InterpStr,
prolog_form: InterpStr,
grounding: [],
confidence: "medium"},
challenge: json{kushya: KushyaStr,
contradicting: "",
strength: "moderate"}}.
%% Teiku response
response_to_json(response(ok, teiku, result(record(Record))), Dict) :-
term_to_string(Record, RecordStr),
Dict = json{status: "teiku",
interpretation: json{text: RecordStr,
prolog_form: RecordStr,
grounding: [],
confidence: "low"},
challenge: null}.
%% No interpretation
response_to_json(response(error, no_interpretation, CaseRef), Dict) :-
atom_string(CaseRef, RefStr),
Dict = json{status: "teiku",
interpretation: json{text: "No interpretation found",
prolog_form: RefStr,
grounding: [],
confidence: "low"},
challenge: null}.
%% No resolution
response_to_json(response(error, no_resolution, CaseRef), Dict) :-
atom_string(CaseRef, RefStr),
Dict = json{status: "teiku",
interpretation: json{text: "No resolution found",
prolog_form: RefStr,
grounding: [],
confidence: "low"},
challenge: null}.
%% Halt response
response_to_json(response(halt, violations(Halts), action(Action)), Dict) :-
maplist(term_to_string, Halts, HaltStrs),
term_to_string(Action, ActionStr),
Dict = json{status: "halt",
violations: HaltStrs,
action: ActionStr}.
%% No halt
response_to_json(response(ok, no_halt, action(Action)), Dict) :-
term_to_string(Action, ActionStr),
Dict = json{status: "ok",
violations: [],
action: ActionStr}.
%% Catch-all for unexpected responses
response_to_json(Response, Dict) :-
term_to_string(Response, Str),
Dict = json{status: "error",
error: json{code: "UNEXPECTED_RESPONSE",
message: Str}}.
%% ============================================================
%% HELPERS
%% ============================================================
%% Parse a root name like "bet-resh-aleph" into an atom bet_resh_aleph
parse_root_name(Name, Atom) :-
( string(Name) -> S = Name
; atom_string(Name, S)
),
split_string(S, "-", "", Parts),
atomic_list_concat(Parts, '_', Atom).
%% Convert any Prolog term to a string
term_to_string(Term, Str) :-
term_string(Term, Str).
%% Map known roots to domains (basic mapping; will be extended)
root_to_domain(bet_resh_aleph, creation).
root_to_domain(yod_tsade_resh, creation).
root_to_domain(aleph_lamed_he, holiness).
root_to_domain(qof_dalet_shin, holiness).
root_to_domain(shin_pe_tet, judgment).
root_to_domain(dalet_yod_nun, judgment).
root_to_domain(chet_samekh_dalet, mercy).
root_to_domain(resh_chet_mem, mercy).
root_to_domain(aleph_vav_resh, light).
root_to_domain(chet_shin_kaf, darkness).