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>
146 lines
4.7 KiB
Prolog
146 lines
4.7 KiB
Prolog
%% socket_server.pl — Unix domain socket listener for Talmudic Engine.
|
|
%%
|
|
%% Construction §3.3: "JSON over Unix domain socket"
|
|
%% Step 14 (Architecture §4): "Connect components"
|
|
%%
|
|
%% Listens on a Unix domain socket, accepts JSON requests,
|
|
%% dispatches to api:process_request/2 via json_bridge,
|
|
%% returns JSON responses.
|
|
%%
|
|
%% Protocol: One JSON object per line (same as Haskell server).
|
|
%% Connection model: accept, handle one or more requests per
|
|
%% connection, close when client disconnects.
|
|
|
|
:- module(socket_server, [
|
|
serve/0,
|
|
serve/1,
|
|
stop_server/0
|
|
]).
|
|
|
|
:- use_module(library(socket)).
|
|
:- use_module(library(json)).
|
|
:- use_module(library(readutil)).
|
|
|
|
:- use_module('../interface/api').
|
|
:- use_module('../interface/json_bridge').
|
|
|
|
%% Default socket path — dev path; production uses /var/run/shorashim/talmudic.sock
|
|
default_socket_path('/tmp/shorashim/talmudic.sock').
|
|
|
|
%% Dynamic flag to control server loop
|
|
:- dynamic server_running/1.
|
|
|
|
%% ============================================================
|
|
%% SERVER ENTRY POINTS
|
|
%% ============================================================
|
|
|
|
%% serve/0 — Start server on default socket path
|
|
serve :-
|
|
default_socket_path(Path),
|
|
serve(Path).
|
|
|
|
%% serve/1 — Start server on given socket path
|
|
serve(Path) :-
|
|
%% Clean up any stale socket file
|
|
( exists_file(Path)
|
|
-> delete_file(Path)
|
|
; true
|
|
),
|
|
%% Ensure parent directory exists
|
|
file_directory_name(Path, Dir),
|
|
( exists_directory(Dir)
|
|
-> true
|
|
; make_directory_path(Dir)
|
|
),
|
|
%% Create Unix domain socket
|
|
unix_domain_socket(ServerSocket),
|
|
tcp_bind(ServerSocket, Path),
|
|
tcp_listen(ServerSocket, 5),
|
|
format('Talmudic Engine listening on ~w~n', [Path]),
|
|
flush_output,
|
|
%% Set running flag
|
|
assert(server_running(true)),
|
|
%% Accept loop
|
|
accept_loop(ServerSocket, Path).
|
|
|
|
%% Stop the server gracefully
|
|
stop_server :-
|
|
retractall(server_running(_)).
|
|
|
|
%% ============================================================
|
|
%% CONNECTION HANDLING
|
|
%% ============================================================
|
|
|
|
%% Main accept loop — waits for connections
|
|
accept_loop(ServerSocket, Path) :-
|
|
( server_running(true)
|
|
-> catch(
|
|
( tcp_accept(ServerSocket, ClientSocket, _Peer),
|
|
tcp_open_socket(ClientSocket, InStream, OutStream),
|
|
%% Handle this client's requests
|
|
catch(
|
|
handle_connection(InStream, OutStream),
|
|
Error,
|
|
( format(user_error, 'Connection error: ~w~n', [Error]),
|
|
flush_output(user_error)
|
|
)
|
|
),
|
|
close(InStream, [force(true)]),
|
|
close(OutStream, [force(true)]),
|
|
accept_loop(ServerSocket, Path)
|
|
),
|
|
AcceptError,
|
|
( format(user_error, 'Accept error: ~w~n', [AcceptError]),
|
|
flush_output(user_error),
|
|
%% Clean up
|
|
tcp_close_socket(ServerSocket),
|
|
( exists_file(Path) -> delete_file(Path) ; true ),
|
|
fail
|
|
)
|
|
)
|
|
; %% Server stopped — clean up
|
|
tcp_close_socket(ServerSocket),
|
|
( exists_file(Path) -> delete_file(Path) ; true ),
|
|
format('Talmudic Engine stopped.~n'),
|
|
flush_output
|
|
).
|
|
|
|
%% Handle a single connection — read lines until EOF
|
|
handle_connection(InStream, OutStream) :-
|
|
read_line_to_string(InStream, Line),
|
|
( Line == end_of_file
|
|
-> true
|
|
; process_json_line(Line, ResponseJson),
|
|
%% Write JSON response on a single line
|
|
json_write_dict(OutStream, ResponseJson),
|
|
nl(OutStream),
|
|
flush_output(OutStream),
|
|
%% Continue reading from this connection
|
|
handle_connection(InStream, OutStream)
|
|
).
|
|
|
|
|
|
%% ============================================================
|
|
%% JSON REQUEST PROCESSING
|
|
%% ============================================================
|
|
|
|
%% Process a JSON line string and produce a JSON response string
|
|
process_json_line(Line, ResponseJson) :-
|
|
catch(
|
|
( %% Parse JSON string to dict
|
|
atom_json_dict(Line, RequestDict, []),
|
|
%% Convert JSON to Prolog request
|
|
json_to_request(RequestDict, Request),
|
|
%% Dispatch to API
|
|
process_request(Request, Response),
|
|
%% Convert response to JSON
|
|
response_to_json(Response, ResponseJson)
|
|
),
|
|
Error,
|
|
( term_string(Error, ErrorStr),
|
|
ResponseJson = json{status: "error",
|
|
error: json{code: "PROCESSING_ERROR",
|
|
message: ErrorStr}}
|
|
)
|
|
).
|