Add Gen.03 organ-systems core: Ada Medium, Soul/Tarot, trust boundary, Hermes MCP bridge

Implements the Gen.03 organ-systems body in SPARK/Ada (Jorvik, No_Exceptions):

- Shared types (mafiabot_types): Bounded_Text, Operation_Status, Organ_Id,
  Provenance_Tag, fixed-point drive/ratio/cost/axis types.
- Config loader: SPARK-safe flat key-value parser (no heap/exceptions/finalization).
- Trust boundary: blocklist substring matching, provenance enforcement,
  tick-based rate limiting, Trust_Guard protected object.
- Soul / Silicon Dawn Tarot: 56-card alphabet, Celtic Cross spread, Big-3
  identity anchors. The cross accretes from the cognitive loops (2 cards per
  layer at steps 6/9/13, stave of 4 at step 17) rather than being drawn whole.
- Sessions: Soul_State is now keyed by (uid, channel, server). Each session
  has its own card pool, cross, and output counter; the Big-3 identity is
  global. A formed cross is held for the session (or 17 outputs, whichever is
  longer). Two users on two channels never share a deck or a cross.
- Ada Medium: 23-step forward-only inference cycle wiring the cognitive loops,
  Celtic Cross injection, and outbound trust screening per session.
- Hermes MCP stdio bridge: JSON-RPC over stdin/stdout (initialize, tools/list,
  tools/call), SOUL.md generation, verbatim id echo. SPARK_Mode Off for I/O;
  trust checks run in proven code first.
- Main driver: organ init -> SOUL.md publish -> MCP serve loop.
- Tests: soul, trust, cycle (session formation/isolation), config.

Fixes an orchestrator bug where step 1 advanced onto Phase_Input after Reset,
failing every cycle.

Note: requires GNAT/Alire to build; no Ada toolchain in this environment, so
compilation and gnatprove must run in CI.
This commit is contained in:
gravermistakes
2026-06-09 21:50:50 +00:00
parent ca3e1c4441
commit 42ef321c4c
24 changed files with 2787 additions and 2 deletions
@@ -0,0 +1,164 @@
package body Config_Loader
with SPARK_Mode => On
is
-- Skip leading/trailing ASCII spaces and tabs in a substring.
procedure Trim_Bounds
(S : in String;
First : in out Positive;
Last : in out Natural)
with Pre => S'First <= First and then Last <= S'Last;
procedure Trim_Bounds
(S : in String;
First : in out Positive;
Last : in out Natural)
is
begin
while First <= Last and then (S (First) = ' ' or else S (First) = ASCII.HT) loop
First := First + 1;
end loop;
while Last >= First and then (S (Last) = ' ' or else S (Last) = ASCII.HT) loop
Last := Last - 1;
end loop;
end Trim_Bounds;
procedure Load_From_Buffer
(Buf : in String;
Store : out Config_Store;
Status : out Operation_Status)
is
Line_Start : Positive := Buf'First;
I : Positive;
Line_End : Natural;
Colon_Pos : Natural;
K_First : Positive;
K_Last : Natural;
V_First : Positive;
V_Last : Natural;
Key_Len : Key_Length;
Val_Len : Val_Length;
begin
Store := (Count => 0,
Entries => (others => (Key => (others => ' '), Key_Len => 0,
Val => (others => ' '), Val_Len => 0)));
Status := OK;
I := Buf'First;
while I <= Buf'Last loop
-- Find end of current line
Line_Start := I;
Line_End := I - 1;
while I <= Buf'Last and then Buf (I) /= ASCII.LF loop
Line_End := I;
I := I + 1;
end loop;
-- Consume newline
if I <= Buf'Last and then Buf (I) = ASCII.LF then
I := I + 1;
end if;
-- Skip blank lines and comments
K_First := Line_Start;
K_Last := Line_End;
Trim_Bounds (Buf, K_First, K_Last);
if K_First > K_Last
or else Buf (K_First) = '#'
then
goto Next_Line;
end if;
-- Find colon separator
Colon_Pos := 0;
for J in K_First .. K_Last loop
if Buf (J) = ':' then
Colon_Pos := J;
exit;
end if;
end loop;
if Colon_Pos = 0 then
goto Next_Line; -- no colon: not a key-value line, skip
end if;
-- Key span
K_First := Line_Start;
K_Last := Colon_Pos - 1;
Trim_Bounds (Buf, K_First, K_Last);
-- Value span
V_First := Colon_Pos + 1;
V_Last := Line_End;
if V_First <= V_Last then
Trim_Bounds (Buf, V_First, V_Last);
end if;
-- Validate lengths
if K_Last < K_First then
goto Next_Line;
end if;
Key_Len := K_Last - K_First + 1;
if Key_Len > Max_Key_Len then
Status := Error_Config;
return;
end if;
if V_Last >= V_First then
Val_Len := V_Last - V_First + 1;
else
Val_Len := 0;
end if;
if Val_Len > Max_Val_Len then
Status := Error_Config;
return;
end if;
-- Check store capacity
if Store.Count = Max_Keys then
Status := Error_Overflow;
return;
end if;
Store.Count := Store.Count + 1;
declare
Idx : constant Entry_Index := Entry_Index (Store.Count);
begin
Store.Entries (Idx).Key_Len := Key_Len;
Store.Entries (Idx).Key (1 .. Key_Len) :=
Buf (K_First .. K_Last);
Store.Entries (Idx).Val_Len := Val_Len;
if Val_Len > 0 then
Store.Entries (Idx).Val (1 .. Val_Len) :=
Buf (V_First .. V_Last);
end if;
end;
<<Next_Line>>
null;
end loop;
end Load_From_Buffer;
function Get_Value
(Store : Config_Store;
Key : String) return Bounded_Text
is
Result : Bounded_Text;
begin
for I in 1 .. Store.Count loop
declare
E : constant Config_Entry := Store.Entries (Entry_Index (I));
begin
if E.Key_Len = Key'Length
and then E.Key (1 .. E.Key_Len) = Key
then
Result.Length := E.Val_Len;
Result.Data (1 .. E.Val_Len) := E.Val (1 .. E.Val_Len);
return Result;
end if;
end;
end loop;
return Result;
end Get_Value;
end Config_Loader;
@@ -0,0 +1,46 @@
-- SPARK-safe flat key-value config parser.
-- Reads "key: value" lines; skips blank lines and comments (# prefix).
-- No heap, no exceptions, no finalization — SPARK_Mode On throughout.
with Mafiabot_Types; use Mafiabot_Types;
package Config_Loader
with SPARK_Mode => On
is
Max_Keys : constant := 64;
Max_Key_Len : constant := 128;
Max_Val_Len : constant := 512;
subtype Key_Length is Natural range 0 .. Max_Key_Len;
subtype Val_Length is Natural range 0 .. Max_Val_Len;
type Config_Entry is record
Key : String (1 .. Max_Key_Len) := (others => ' ');
Key_Len : Key_Length := 0;
Val : String (1 .. Max_Val_Len) := (others => ' ');
Val_Len : Val_Length := 0;
end record;
type Entry_Index is range 1 .. Max_Keys;
subtype Entry_Count is Natural range 0 .. Max_Keys;
type Config_Store is record
Entries : array (Entry_Index) of Config_Entry :=
(others => (Key => (others => ' '), Key_Len => 0,
Val => (others => ' '), Val_Len => 0));
Count : Entry_Count := 0;
end record;
-- Parse Buf (a complete file read into a string) into Store.
procedure Load_From_Buffer
(Buf : in String;
Store : out Config_Store;
Status : out Operation_Status)
with Pre => Buf'Length > 0;
-- Retrieve the value for Key; returns empty Bounded_Text if not found.
function Get_Value
(Store : Config_Store;
Key : String) return Bounded_Text;
end Config_Loader;