Step 13: Korban Processor complete — 7 modules, 181 tests, all passing

- semichah: provenance tagging (6 sources, rejects empty/anonymous)
- shechitah: strip preamble, self-reference, hedging, markdown, sycophancy
- zerikah: extract Torah refs, Hebrew text, root analysis, insight markers
- haktarah: transform prose to Prolog propositions (verse/3, root/2, claim/2, connection/3)
- router: stateless routing — removed module-level _routing_log (kiyyor principle)
- model_interface: outbound Claude API via httpx (Construction §3.4, HTTPS only)
- hebrew_parser: nikkud/teamim stripping, consonant extraction, gematria, root identification

All 5 BUILD_KORBAN.md verification tests pass.
AGENTS.md: added structure_not_content.md to spec table, added Principle 6.
build_log.md: recorded session, broken tablets #18-20, design decisions.

Ka'asher tzivah Hashem et Moshe.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott KarpandClaude Sonnet 4.6 committed 2026-03-09 09:37:25 -04:00
1 parent 0f8ff56f74
commit 33f8b03583
16 files changed
+1816 -31

No files matched your search

@@ -0,0 +1,176 @@
"""
Tests for model_interface — outbound API calls.
Construction §3.4: outbound HTTPS only, no inbound.
No live API calls in tests — use mocked httpx responses.
Kiyyor principle: stateless functions, no connection pooling between calls.
"""
import json
import os
import pytest
import httpx
from korban_processor.model_interface import (
ModelRequest,
ModelResponse,
call_claude,
CLAUDE_MODEL,
CLAUDE_MAX_TOKENS,
ANTHROPIC_VERSION,
)
class TestModelRequest:
def test_default_max_tokens(self):
req = ModelRequest(query="test")
assert req.max_tokens == CLAUDE_MAX_TOKENS
def test_no_system_prompt_by_default(self):
req = ModelRequest(query="test")
assert req.system_prompt is None
def test_custom_max_tokens(self):
req = ModelRequest(query="test", max_tokens=1024)
assert req.max_tokens == 1024
def test_with_system_prompt(self):
req = ModelRequest(query="test", system_prompt="You analyze Torah.")
assert req.system_prompt == "You analyze Torah."
class TestModelResponseShape:
def test_model_response_fields(self):
resp = ModelResponse(
content="בְּרֵאשִׁית means 'in the beginning'.",
model=CLAUDE_MODEL,
stop_reason="end_turn",
input_tokens=10,
output_tokens=50,
)
assert resp.content
assert resp.model == CLAUDE_MODEL
assert resp.stop_reason == "end_turn"
assert resp.input_tokens == 10
assert resp.output_tokens == 50
class TestCallClaudeRequiresApiKey:
def test_raises_without_api_key(self, monkeypatch):
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
with pytest.raises(ValueError, match="ANTHROPIC_API_KEY"):
call_claude(ModelRequest(query="test"))
class TestCallClaudeMocked:
"""Test call_claude with mocked httpx transport — no live API calls."""
def _make_api_response(self, text: str) -> dict:
return {
"id": "msg_test",
"type": "message",
"role": "assistant",
"model": CLAUDE_MODEL,
"content": [{"type": "text", "text": text}],
"stop_reason": "end_turn",
"usage": {"input_tokens": 20, "output_tokens": 40},
}
def test_sends_correct_model(self, monkeypatch, respx_mock=None):
"""Verify the request uses the specified Claude model."""
# Test via request inspection using httpx transport mock
captured_requests = []
class CapturingTransport(httpx.BaseTransport):
def handle_request(self, request):
captured_requests.append(request)
body = self._make_api_response(
"The root ר-א-ש means head. Genesis 1:1."
)
return httpx.Response(
200,
content=json.dumps(body).encode(),
headers={"content-type": "application/json"},
request=request,
)
def _make_api_response(self, text):
return {
"id": "msg_test",
"type": "message",
"role": "assistant",
"model": CLAUDE_MODEL,
"content": [{"type": "text", "text": text}],
"stop_reason": "end_turn",
"usage": {"input_tokens": 20, "output_tokens": 40},
}
import korban_processor.model_interface as mi
original_client = httpx.Client
monkeypatch.setenv("ANTHROPIC_API_KEY", "test-key-123")
def patched_client(**kwargs):
return original_client(transport=CapturingTransport(), **{
k: v for k, v in kwargs.items() if k not in ("transport",)
})
monkeypatch.setattr(httpx, "Client", patched_client)
req = ModelRequest(query="What does bereishit mean?")
resp = mi.call_claude(req)
assert len(captured_requests) == 1
body = json.loads(captured_requests[0].content)
assert body["model"] == CLAUDE_MODEL
assert body["max_tokens"] == CLAUDE_MAX_TOKENS
def test_correct_anthropic_headers(self, monkeypatch):
"""Verify the request sends correct Anthropic API headers."""
captured_requests = []
class HeaderCapture(httpx.BaseTransport):
def handle_request(self, request):
captured_requests.append(request)
body = {
"id": "msg_test",
"type": "message",
"role": "assistant",
"model": CLAUDE_MODEL,
"content": [{"type": "text", "text": "test response"}],
"stop_reason": "end_turn",
"usage": {"input_tokens": 5, "output_tokens": 10},
}
return httpx.Response(
200,
content=json.dumps(body).encode(),
headers={"content-type": "application/json"},
request=request,
)
monkeypatch.setenv("ANTHROPIC_API_KEY", "test-key-abc")
import korban_processor.model_interface as mi
original_client = httpx.Client
def patched_client(**kwargs):
return original_client(transport=HeaderCapture(), **{
k: v for k, v in kwargs.items() if k not in ("transport",)
})
monkeypatch.setattr(httpx, "Client", patched_client)
mi.call_claude(ModelRequest(query="test"))
req = captured_requests[0]
assert req.headers["x-api-key"] == "test-key-abc"
assert req.headers["anthropic-version"] == ANTHROPIC_VERSION
assert req.headers["content-type"] == "application/json"
def test_outbound_only_https(self, monkeypatch):
"""Construction §3.4: outbound HTTPS only. Verify the URL is HTTPS."""
import korban_processor.model_interface as mi
assert mi.CLAUDE_API_URL.startswith("https://"), \
"Claude API URL must use HTTPS — outbound HTTPS only per Construction §3.4"