chisel/step-service/service.py
maverick 00faa800e0 D: wire STEP engine — shared-secret auth to build123d service
- step.ts sends X-Chisel-Secret; index.ts passes STEP_SHARED_SECRET
- step-service/ (FastAPI + runner.py) runs build123d on the VM, exports STEP
- Live: chisel-step.theradicalparty.com via dedicated cloudflared tunnel
- Production /api/step verified end-to-end → valid ISO-10303-21 STEP

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 00:57:08 +10:00

70 lines
2.2 KiB
Python

"""Chisel STEP engine — executes build123d code and returns a STEP file.
POST /run { "code": "<build123d python assigning `result`>" }
→ 200 application/step (the file) or 4xx/5xx JSON error
Auth: requests must carry X-Chisel-Secret: <STEP_SHARED_SECRET>. Only Chisel's
Cloudflare Worker knows it, so the arbitrary-code /run endpoint isn't public.
Runs each script in a subprocess (runner.py) with a hard timeout so a bad or
malicious model can't hang or take down the service.
"""
import os
import subprocess
import tempfile
from fastapi import FastAPI, Header, HTTPException
from fastapi.responses import Response
from pydantic import BaseModel
HERE = os.path.dirname(os.path.abspath(__file__))
PY = os.path.join(HERE, "venv", "bin", "python")
RUNNER = os.path.join(HERE, "runner.py")
SECRET = os.environ.get("STEP_SHARED_SECRET", "")
TIMEOUT = 40
app = FastAPI(title="Chisel STEP engine")
class RunReq(BaseModel):
code: str
@app.get("/health")
def health():
return {"ok": True}
@app.post("/run")
def run(req: RunReq, x_chisel_secret: str = Header(default="")):
if SECRET and x_chisel_secret != SECRET:
raise HTTPException(status_code=401, detail="bad secret")
with tempfile.TemporaryDirectory() as d:
code_file = os.path.join(d, "model.py")
out_file = os.path.join(d, "out.step")
with open(code_file, "w") as f:
f.write(req.code)
try:
proc = subprocess.run(
[PY, RUNNER, code_file, out_file],
capture_output=True,
text=True,
timeout=TIMEOUT,
)
except subprocess.TimeoutExpired:
raise HTTPException(status_code=504, detail="model execution timed out")
if proc.returncode != 0 or not os.path.exists(out_file):
msg = (proc.stderr or proc.stdout or "unknown error").strip()
raise HTTPException(status_code=422, detail=msg[-800:])
with open(out_file, "rb") as f:
data = f.read()
return Response(
content=data,
media_type="application/step",
headers={"content-disposition": 'attachment; filename="chisel-model.step"'},
)