Lands the Phase C "make cognition legible" slice plus Phase A residue, all backend-reader-first over real engine data (no theater, read-only doctrine intact, zero serving-path imports). C1-a — Cognitive pipeline record (persistence-first, per #729 worthiness edit): - workbench/pipeline_record.py: curated CognitivePipelineRecord over the real CognitiveTurnResult (input → intent → proposition_graph → articulation_target → realizer → walk_telemetry → trace_hash). Raw field multivectors are DELIBERATELY excluded; _assert_no_raw_field_payload recursively rejects raw field keys, and validate_pipeline_record fails closed on missing/duplicate stages, non-recorded status, or dangling edges — the UI can never receive a partial record that claims to be complete. - test_workbench_pipeline_record.py: non-vacuous guards — missing stage, monkeypatched new required stage, and injected raw {"F": [...]} each raise. C2-a — Contemplation as a process: /contemplation route over real persisted contemplation/runs/*.json (glob reader; honest-empty when absent). C4-a — Identity continuity (L10/L11): RunDetail.identity_continuity + Runs Identity tab, sourced from the real core.engine_identity (engine_identity / parent_engine_identity lineage relation, re-derived to verify). Demo Theater: renders backend-owned proof-promotion + entailment DAGs. Phase A residue: density preference wired end-to-end (settings → shell → tokens); cross-route consistency touch-ups. Infra: local API CORS now echoes only validated 127.0.0.1/localhost origins (hostname-checked, not arbitrary reflection) so Vite fallback ports work. Route chunk-split keeps the build warning-free. Cleanup: corrected the stale ADR-0175 practice-lane assertions (build_report is 6 correct / 0 wrong / 44 refused after the current serving lane; wrong=0 held) and the two registry-derived count tests (LeftNav + CommandPalette 12 → 13 for the new Contemplation route). Docs: runtime_contracts.md (pipeline-record contract), UI-UX-GUIDE, api-contract-v1, data-shapes-v1, wave-M-worthiness, phase-a-residue-ledger. Validation: 106 workbench/practice Python tests green (incl. wrong=0 lane + pipeline-record fail-closed guards); 459/459 frontend; pnpm build clean; git diff --check clean. No generate.derivation / reliability_gate / stream / field.propagate / vault.store imports.
128 lines
4.5 KiB
Python
128 lines
4.5 KiB
Python
"""Stdlib HTTP server entrypoint for CORE Workbench W-026."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import sys
|
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
|
|
from workbench.api import MAX_CHAT_BODY_BYTES, WorkbenchApi
|
|
|
|
|
|
def _local_origin_or_default(origin: str) -> str:
|
|
if not origin:
|
|
return "http://127.0.0.1:5173"
|
|
import urllib.parse
|
|
|
|
parsed_origin = urllib.parse.urlparse(origin)
|
|
if parsed_origin.hostname in {"127.0.0.1", "localhost"}:
|
|
return origin
|
|
return "http://127.0.0.1:5173"
|
|
|
|
|
|
class WorkbenchRequestHandler(BaseHTTPRequestHandler):
|
|
api = WorkbenchApi()
|
|
|
|
def do_GET(self) -> None: # noqa: N802 - stdlib handler API
|
|
self._handle()
|
|
|
|
def do_POST(self) -> None: # noqa: N802 - stdlib handler API
|
|
self._handle()
|
|
|
|
def do_OPTIONS(self) -> None: # noqa: N802 - stdlib handler API
|
|
self.send_response(204)
|
|
self._send_common_headers(0)
|
|
self.end_headers()
|
|
|
|
def log_message(self, format: str, *args: object) -> None:
|
|
if os.environ.get("CORE_WORKBENCH_QUIET") == "1":
|
|
return
|
|
sys.stderr.write(
|
|
"%s - - [%s] %s\n"
|
|
% (self.address_string(), self.log_date_time_string(), format % args)
|
|
)
|
|
|
|
def _handle(self) -> None:
|
|
host = self.headers.get("Host", "")
|
|
origin = self.headers.get("Origin", "")
|
|
|
|
host_name = host.split(":")[0] if ":" in host else host
|
|
if host_name not in {"127.0.0.1", "localhost"}:
|
|
payload = json.dumps({"ok": False, "error": "CORS check failed: Host is non-local"}).encode("utf-8")
|
|
self.send_response(400)
|
|
self._send_common_headers(len(payload))
|
|
self.end_headers()
|
|
self.wfile.write(payload)
|
|
return
|
|
|
|
if origin:
|
|
import urllib.parse
|
|
parsed_origin = urllib.parse.urlparse(origin)
|
|
origin_host = parsed_origin.hostname
|
|
if origin_host not in {"127.0.0.1", "localhost"}:
|
|
payload = json.dumps({"ok": False, "error": "CORS check failed: Origin is non-local"}).encode("utf-8")
|
|
self.send_response(400)
|
|
self._send_common_headers(len(payload))
|
|
self.end_headers()
|
|
self.wfile.write(payload)
|
|
return
|
|
|
|
try:
|
|
length = max(0, int(self.headers.get("Content-Length") or "0"))
|
|
except ValueError:
|
|
length = 0
|
|
if (
|
|
self.command == "POST"
|
|
and self.path.split("?", 1)[0].rstrip("/") == "/chat/turn"
|
|
and length > MAX_CHAT_BODY_BYTES
|
|
):
|
|
body = b"x" * (MAX_CHAT_BODY_BYTES + 1)
|
|
else:
|
|
body = self.rfile.read(length) if length else b""
|
|
response = self.api.handle(self.command, self.path, body)
|
|
payload = json.dumps(response.payload, ensure_ascii=False, sort_keys=True).encode("utf-8")
|
|
self.send_response(response.status)
|
|
self._send_common_headers(len(payload))
|
|
self.end_headers()
|
|
self.wfile.write(payload)
|
|
|
|
def _send_common_headers(self, content_length: int) -> None:
|
|
self.send_header("Content-Type", "application/json; charset=utf-8")
|
|
self.send_header("Content-Length", str(content_length))
|
|
self.send_header(
|
|
"Access-Control-Allow-Origin",
|
|
_local_origin_or_default(self.headers.get("Origin", "")),
|
|
)
|
|
self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
|
self.send_header("Access-Control-Allow-Headers", "Content-Type")
|
|
|
|
|
|
def serve(*, host: str = "127.0.0.1", port: int = 8765) -> None:
|
|
server = ThreadingHTTPServer((host, port), WorkbenchRequestHandler)
|
|
print(f"CORE Workbench API listening on http://{host}:{port}")
|
|
try:
|
|
server.serve_forever()
|
|
except KeyboardInterrupt:
|
|
print()
|
|
raise SystemExit(0)
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
parser = argparse.ArgumentParser(description="CORE Workbench local API")
|
|
parser.add_argument("--host", default="127.0.0.1")
|
|
parser.add_argument("--port", type=int, default=8765)
|
|
parser.add_argument(
|
|
"--allow-nonlocal-bind",
|
|
action="store_true",
|
|
help="allow binding to a host other than 127.0.0.1 or localhost",
|
|
)
|
|
args = parser.parse_args(argv)
|
|
if args.host not in {"127.0.0.1", "localhost"} and not args.allow_nonlocal_bind:
|
|
parser.error("non-local bind requires --allow-nonlocal-bind")
|
|
serve(host=args.host, port=args.port)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|