Port the 6 audit-missing fns name-preserving into src/go/core-server (make_server, log_message, _send_json, _send_ndjson, do_GET, do_POST; main via Main/os.Args surface): the full HTTP sidecar surface with byte-identical wire bodies (vendored pyjson PyDump: json.dumps ', '/': ' separators + ensure_ascii + PyDict insertion order), CPython json scanner error messages, int()/parse_qs/truthiness/or-laws, str[:200] codepoint truncation, and the AttributeError crash-parity class. Truth: 57-case shared-harness envelope (scripts/gen_core_server_truth.py under scripts/cs_freeze_seams — pinned uuid/time on module attrs, rig-bound probe seams exercising the REAL handler methods) verified byte-identical x5; go test 9/9 (verbatim TestFrozenTruthReplay + 8 quirk tests); CI replay binary 57/57; live HTTP parity 24/24 byte-identical (scripts/check_core_server_parity.py — bodies exact under pinned id/created, not modulo-volatile); canary_rune_serve 28/28; gofmt/vet/build clean; ruff clean; shims pytest zero-delta vs stashed base (9 fresh-worktree sibling-kernel failures pre-existing). Dichotomy audit core_server 6/12 PARTIAL -> 12/12 FULL (107/192).
228 lines
8.5 KiB
Python
228 lines
8.5 KiB
Python
#!/usr/bin/env python3
|
|
"""check_core_server_parity.py — live HTTP parity: the Python oracle
|
|
(core_server.make_server) vs the GO port (src/go/core-server serve),
|
|
byte-exact bodies under pinned uuid/time (scripts/cs_freeze_seams).
|
|
|
|
Both servers run the SAME spec-built stub operator; the Go process gets
|
|
the identical spec via RUNE_CS_OPERATOR_SPEC plus the id/created pins
|
|
via RUNE_CS_CHUNK_HEX / RUNE_FROZEN_NOW, so every response body —
|
|
including chat.completion ids, created epochs, NDJSON framing, error
|
|
envelopes, and Python json.dumps separator bytes — must match the
|
|
oracle BYTE FOR BYTE (not modulo-volatile like the canary).
|
|
|
|
Run: python3 scripts/check_core_server_parity.py
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import http.client
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import threading
|
|
import time
|
|
from pathlib import Path
|
|
|
|
REPO = Path(__file__).resolve().parents[1]
|
|
|
|
# Pull in the freeze rig (auto-imported when PYTHONPATH includes it; the
|
|
# explicit import covers bare invocation) — it pins uuid/time on the
|
|
# oracle module BEFORE import and exposes the stub operator + constants.
|
|
sys.path.insert(0, str(REPO / "scripts" / "cs_freeze_seams"))
|
|
import sitecustomize # noqa: E402 (the rig; pins apply on first import)
|
|
|
|
from rune import core_server # noqa: E402 (after the rig pins)
|
|
|
|
FROZEN_HEX = sitecustomize.FROZEN_HEX
|
|
FROZEN_NOW = sitecustomize.FROZEN_NOW
|
|
|
|
SPEC = {
|
|
"models": [{"name": "stub-a", "size_gb": 4.1}, {"size_gb": 2}, {}],
|
|
"status": {"identity": "rune", "role": "operator", "current_task": None,
|
|
"active_model": "stub-a", "tasks_pending": 0,
|
|
"tasks_completed": 3},
|
|
"send": {"task_id": "task-1", "status": "completed", "model": "stub-model",
|
|
"user_text": "Canned reply.", "degraded": [], "iterations_used": 1},
|
|
"window": [{"role": "user", "content": "m1"},
|
|
{"role": "assistant", "content": "m2"},
|
|
{"role": "user", "content": "m3"}],
|
|
}
|
|
SPEC_RAISE = {"raise_status": "boom", "raise_send": "boom send"}
|
|
|
|
PASS = 0
|
|
FAIL = 0
|
|
|
|
|
|
def test(name: str, ok: bool, detail: str = "") -> None:
|
|
global PASS, FAIL
|
|
if ok:
|
|
PASS += 1
|
|
print(f" ✓ {name}")
|
|
else:
|
|
FAIL += 1
|
|
print(f" ✗ {name} — {detail}")
|
|
|
|
|
|
def request(port: int, method: str, path: str, body: str | None = None):
|
|
"""One raw request → (status, content-type, body-bytes) or NO_RESPONSE."""
|
|
try:
|
|
conn = http.client.HTTPConnection("127.0.0.1", port, timeout=10)
|
|
headers = {}
|
|
data = None
|
|
if body is not None:
|
|
data = body.encode()
|
|
headers["Content-Type"] = "application/json"
|
|
conn.request(method, path, body=data, headers=headers)
|
|
resp = conn.getresponse()
|
|
out = (resp.status, resp.getheader("Content-Type"), resp.read())
|
|
conn.close()
|
|
return out
|
|
except Exception: # noqa: BLE001 — the crash-parity class IS the contract
|
|
return ("NO_RESPONSE", None, b"")
|
|
|
|
|
|
def wait_ready(port: int, timeout: float = 10.0) -> bool:
|
|
deadline = time.time() + timeout
|
|
while time.time() < deadline:
|
|
r = request(port, "GET", "/v1/status")
|
|
if r[0] != "NO_RESPONSE":
|
|
return True
|
|
time.sleep(0.1)
|
|
return False
|
|
|
|
|
|
def free_port() -> int:
|
|
import socket
|
|
s = socket.socket()
|
|
s.bind(("127.0.0.1", 0))
|
|
port = s.getsockname()[1]
|
|
s.close()
|
|
return port
|
|
|
|
|
|
class PyServer:
|
|
"""The oracle on a real socket with the rig's spec-built stub."""
|
|
|
|
def __init__(self, spec: dict):
|
|
self.port = free_port()
|
|
self.server = core_server.make_server(
|
|
"127.0.0.1", self.port, sitecustomize._stub_operator(spec))
|
|
self.thread = threading.Thread(target=self.server.serve_forever, daemon=True)
|
|
|
|
def __enter__(self) -> int:
|
|
self.thread.start()
|
|
assert wait_ready(self.port)
|
|
return self.port
|
|
|
|
def __exit__(self, *exc) -> None:
|
|
self.server.shutdown()
|
|
|
|
|
|
class GoServer:
|
|
def __init__(self, spec: dict):
|
|
self.port = free_port()
|
|
env = dict(os.environ)
|
|
env["RUNE_CS_OPERATOR_SPEC"] = json.dumps(spec)
|
|
env["RUNE_CS_CHUNK_HEX"] = FROZEN_HEX
|
|
env["RUNE_FROZEN_NOW"] = FROZEN_NOW
|
|
self.proc = subprocess.Popen(
|
|
["/tmp/cs-parity-serve", "--host", "127.0.0.1",
|
|
"--port", str(self.port)],
|
|
env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
|
|
|
|
def __enter__(self) -> int:
|
|
assert wait_ready(self.port)
|
|
return self.port
|
|
|
|
def __exit__(self, *exc) -> None:
|
|
self.proc.terminate()
|
|
try:
|
|
self.proc.wait(timeout=5)
|
|
except subprocess.TimeoutExpired:
|
|
self.proc.kill()
|
|
|
|
|
|
CHAT_BODY = json.dumps({"model": "m2", "messages": [
|
|
{"role": "user", "content": "first"},
|
|
{"role": "assistant", "content": "mid"},
|
|
{"role": "user", "content": "final"}]})
|
|
|
|
|
|
def cases() -> list[tuple[str, str, str, str | None]]:
|
|
return [
|
|
("models", "GET", "/v1/models", None),
|
|
("status", "GET", "/v1/status", None),
|
|
("conversation", "GET", "/v1/conversation?channel=ops&n=2", None),
|
|
("conversation-defaults", "GET", "/v1/conversation", None),
|
|
("conversation-bad-n", "GET", "/v1/conversation?n=abc", None),
|
|
("conversation-pct", "GET", "/v1/conversation?channel=%C3%A9+d", None),
|
|
("models-fullpath-mismatch", "GET", "/v1/models?x=1", None),
|
|
("not-found", "GET", "/v1/nope", None),
|
|
("approve", "POST", "/v1/approvals/resolve", '{"approved": true}'),
|
|
("approve-truthy-string", "POST", "/v1/approvals/resolve", '{"approved": "yes"}'),
|
|
("approve-empty-body", "POST", "/v1/approvals/resolve", ""),
|
|
("approve-bad-json", "POST", "/v1/approvals/resolve", "not json"),
|
|
("approve-trailing-comma", "POST", "/v1/approvals/resolve",
|
|
'{"approved": true,}'),
|
|
("chat", "POST", "/v1/chat/completions", CHAT_BODY),
|
|
("chat-stream", "POST", "/v1/chat/completions",
|
|
'{"messages": [{"role": "user", "content": "streamed"}], "stream": true}'),
|
|
("chat-stream-truthy-string", "POST", "/v1/chat/completions",
|
|
'{"messages": [{"role": "user", "content": "x"}], "stream": "yes"}'),
|
|
("chat-no-user", "POST", "/v1/chat/completions",
|
|
'{"messages": [{"role": "assistant", "content": "no"}]}'),
|
|
("chat-numeric-content", "POST", "/v1/chat/completions",
|
|
'{"messages": [{"role": "user", "content": 5}]}'),
|
|
("chat-crash-parity", "POST", "/v1/chat/completions",
|
|
'{"messages": "abc"}'),
|
|
("chat-model-empty", "POST", "/v1/chat/completions",
|
|
'{"model": "", "messages": [{"role": "user", "content": "x"}]}'),
|
|
("post-not-found", "POST", "/v1/other", "{}"),
|
|
]
|
|
|
|
|
|
def sweep(py_port: int, go_port: int) -> None:
|
|
for name, method, path, body in cases():
|
|
py = request(py_port, method, path, body)
|
|
go = request(go_port, method, path, body)
|
|
ok = py == go
|
|
detail = ""
|
|
if not ok:
|
|
detail = (f"py=({py[0]!r},{py[1]!r}) {py[2][:160]!r} "
|
|
f"go=({go[0]!r},{go[1]!r}) {go[2][:160]!r}")
|
|
test(f"parity: {name} byte-identical", ok, detail)
|
|
|
|
|
|
def main() -> int:
|
|
print("=" * 55)
|
|
print("core_server live parity — Python oracle vs GO port (pinned)")
|
|
print("=" * 55)
|
|
|
|
go_dir = REPO / "src" / "go" / "core-server"
|
|
r = subprocess.run(["go", "build", "-o", "/tmp/cs-parity-serve", "."],
|
|
cwd=go_dir, capture_output=True, text=True, timeout=300)
|
|
test("go build exits 0", r.returncode == 0, r.stderr[-300:])
|
|
|
|
with PyServer(SPEC) as py_port, GoServer(SPEC) as go_port:
|
|
print(f"py oracle :{py_port} go port :{go_port}")
|
|
sweep(py_port, go_port)
|
|
|
|
with PyServer(SPEC_RAISE) as py_port, GoServer(SPEC_RAISE) as go_port:
|
|
py = request(py_port, "GET", "/v1/status")
|
|
go = request(go_port, "GET", "/v1/status")
|
|
test("parity: raise-status 500 envelope byte-identical", py == go,
|
|
f"py={py[2][:160]!r} go={go[2][:160]!r}")
|
|
py = request(py_port, "POST", "/v1/chat/completions",
|
|
'{"messages": [{"role": "user", "content": "x"}]}')
|
|
go = request(go_port, "POST", "/v1/chat/completions",
|
|
'{"messages": [{"role": "user", "content": "x"}]}')
|
|
test("parity: raise-send 500 envelope byte-identical", py == go,
|
|
f"py={py[2][:160]!r} go={go[2][:160]!r}")
|
|
|
|
print(f"\nRESULTS: {PASS} pass, {FAIL} fail")
|
|
return 1 if FAIL else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|