...]; v1 = HMAC-SHA256(secret, f"{t}.{payload}")."""
+ if not header or not secret: return False
+ parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
+ t = parts.get("t"); sigs = [p.split("=", 1)[1] for p in header.split(",") if p.startswith("v1=")]
+ if not t or not sigs or not t.isdigit(): return False
+ if abs((now_ts or time.time()) - int(t)) > tolerance: return False
+ expected = hmac.new(secret.encode(), f"{t}.".encode() + payload, hashlib.sha256).hexdigest()
+ return any(hmac.compare_digest(expected, s) for s in sigs)
+
+def stripe_sign(payload: bytes, secret: str, ts=None):
+ """Produce a Stripe-Signature header (used by tests and the README's rotation check)."""
+ ts = int(ts or time.time()); sig = hmac.new(secret.encode(), f"{ts}.".encode() + payload, hashlib.sha256).hexdigest()
+ return f"t={ts},v1={sig}"
+
+# ----------------------------------------------------------------------------- application
+
+class App:
+ def __init__(self, cfg: Config):
+ self.cfg = cfg; self.db = DB(cfg.db)
+ os.makedirs(cfg.blobs, exist_ok=True)
+ self.grant_lock = threading.Lock()
+ self.public_url = cfg.public_url or "http://" + cfg.bind
+ self.launch_env = os.path.join(os.path.dirname(os.path.abspath(cfg.blobs)), "launch.env")
+ self.launcher = RunPodLauncher(cfg, public_url=self.public_url) if cfg.runpod_api_key else None # tests inject FakeLauncher
+ self.reaper_fail = {} # pod_id -> consecutive reaper delete failures (in memory; 3 = reaper.stuck)
+ self.last_reap = 0.0
+ self._bg = []; self._bg_lock = threading.Lock()
+
+ # -- background work (pod create/delete never blocks an HTTP response); join_bg() is for tests
+ def spawn(self, target, *args):
+ t = threading.Thread(target=target, args=args, daemon=True)
+ with self._bg_lock:
+ self._bg = [x for x in self._bg if x.is_alive()]; self._bg.append(t)
+ t.start(); return t
+ def join_bg(self, timeout=10):
+ deadline = time.time() + timeout
+ while True:
+ with self._bg_lock: live = [x for x in self._bg if x.is_alive()]
+ if not live or time.time() > deadline: return not live
+ live[0].join(max(0.01, deadline - time.time()))
+
+ # -- blob URLs stand in for presigned object-storage URLs: HMAC over name|exp
+ def blob_path(self, name): return os.path.join(self.cfg.blobs, name)
+ def blob_sig(self, name, exp): return hmac.new(self.db.meta("blob_secret").encode(), f"{name}|{exp}".encode(), hashlib.sha256).hexdigest()[:32]
+ def blob_url(self, name, ttl=BLOB_TTL_S):
+ exp = int(time.time()) + ttl; return f"{self.public_url}/blob/{name}?exp={exp}&sig={self.blob_sig(name, exp)}", exp
+ def blob_ok(self, name, query):
+ q = parse_qs(query); exp = (q.get("exp") or [""])[0]; sig = (q.get("sig") or [""])[0]
+ if not exp.isdigit() or int(exp) < time.time(): return False
+ return hmac.compare_digest(self.blob_sig(name, int(exp)), sig)
+
+ # -- purchase: one idempotent handler for /welcome and the webhook
+ def grant(self, sess, source):
+ """Credit a paid Checkout Session once. Returns dict(kind, key_id, full_key|None, usd, gpu_h, balance)."""
+ sid = sess.get("id");
+ if not sid: raise ValueError("session has no id")
+ if sess.get("payment_status") != "paid": raise ValueError("payment_status=%s" % sess.get("payment_status"))
+ with self.grant_lock:
+ existing = self.db.one("select * from credits where session_id=?", sid)
+ if existing:
+ pend = self.db.one("select * from pending_keys where session_id=?", sid)
+ full = None
+ if pend and source == "welcome": # first visit after a webhook-first grant: show once
+ full = pend["full_key"]; self.db.x("delete from pending_keys where session_id=?", sid)
+ log("key.revealed", key_id=existing["key_id"], session=sid)
+ return {"kind": "already", "key_id": existing["key_id"], "full_key": full, "usd": existing["usd"],
+ "gpu_h": existing["gpu_s"] / 3600, "balance": self.db.balance(existing["key_id"])}
+ price_id, usd, gpu_h = self.pack_for(sess)
+ ref = (sess.get("client_reference_id") or "").strip()
+ details = sess.get("customer_details") or {}
+ email = details.get("email") or sess.get("customer_email")
+ k = self.db.key_by_id(ref) if ref else None
+ if k:
+ self.db.add_credit(sid, k["key_id"], usd, gpu_h * 3600, price_id)
+ log("credit.topup", key_id=k["key_id"], usd=usd, gpu_h=gpu_h, price_id=price_id, session=sid, source=source)
+ return {"kind": "topup", "key_id": k["key_id"], "full_key": None, "usd": usd, "gpu_h": gpu_h, "balance": self.db.balance(k["key_id"])}
+ full, kid = self.db.create_key(email=email, label="stripe:" + sid[-8:])
+ self.db.add_credit(sid, kid, usd, gpu_h * 3600, price_id)
+ if source != "welcome": # buyer has not seen the key yet: hold it for the welcome page
+ self.db.x("insert or replace into pending_keys(session_id,key_id,full_key,created) values(?,?,?,?)", sid, kid, full, now())
+ log("credit.new_key", key_id=kid, usd=usd, gpu_h=gpu_h, price_id=price_id, session=sid, source=source, has_email=bool(email))
+ return {"kind": "new", "key_id": kid, "full_key": full, "usd": usd, "gpu_h": gpu_h, "balance": self.db.balance(kid)}
+
+ def pack_for(self, sess):
+ """price id decides the hours (coupons do not reduce them); usd credited = hours * rate."""
+ rate = self.cfg.rates.get("any", 2.0)
+ price_id = None
+ try: price_id = sess["line_items"]["data"][0]["price"]["id"]
+ except (KeyError, IndexError, TypeError): pass
+ if price_id in self.cfg.packs:
+ _, gpu_h = self.cfg.packs[price_id]; return price_id, round(gpu_h * rate, 2), gpu_h
+ # Unknown price id: honor the amount actually paid at the base rate, and say so in the log.
+ usd = (sess.get("amount_total") or 0) / 100.0
+ log("pack.unknown_price", price_id=price_id, amount_total=sess.get("amount_total"))
+ return price_id, round(usd, 2), round(usd / rate, 4)
+
+ def purge_pending(self):
+ cutoff = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(time.time() - PENDING_KEY_TTL_S))
+ self.db.x("delete from pending_keys where created < ?", cutoff)
+
+ # -- pods: one per job, created after `start`, deleted at every terminal state
+ def launch_job(self, jid, token, wall, gpu):
+ """Background: walk the launcher's ladder; queued -> launching(pod_id) or failed:no_capacity (unbilled)."""
+ try: pod_id = self.launcher.create(jid, token, wall, gpu)
+ except NoCapacity as e:
+ log("job.no_capacity", job=jid, error=str(e)); self.fail_unlaunched(jid); return
+ except Exception as e:
+ log("launch.error", job=jid, error=repr(e)); self.fail_unlaunched(jid); return
+ j = self.db.job(jid)
+ if not j: self.release_pod(jid, pod_id, "job_vanished"); return
+ if j["state"] == "queued": self.db.set_job(jid, state="launching", pod_id=pod_id, launched_at=now())
+ elif j["state"] in ("launching", "running"): self.db.set_job(jid, pod_id=pod_id, launched_at=j["launched_at"] or now())
+ else: # cancelled while the create call was in flight: no orphan
+ self.db.set_job(jid, pod_id=pod_id); self.release_pod(jid, pod_id, "terminal_during_launch"); return
+ log("job.launching", job=jid, key_id=j["key_id"], pod_id=pod_id)
+
+ def fail_unlaunched(self, jid):
+ j = self.db.job(jid)
+ if j and j["state"] in ("queued", "launching"):
+ self.db.set_job(jid, state="failed", finished=now(), error="no_capacity", token_hash=None)
+
+ def release_pod(self, jid, pod_id, reason):
+ """Delete the job's pod in the background (never from the request thread)."""
+ if not self.launcher or not pod_id: return
+ def run():
+ try: self.launcher.delete(pod_id); log("pod.deleted", job=jid, pod_id=pod_id, reason=reason)
+ except Exception as e: log("pod.delete_failed", job=jid, pod_id=pod_id, reason=reason, error=repr(e))
+ self.spawn(run)
+
+ def finish_job(self, j, log_ev, **fields):
+ """Terminal write + pod release + log, in that order (state first so a crash mid-way leaves the reaper a terminal job)."""
+ self.db.set_job(j["id"], **fields)
+ cur = self.db.job(j["id"])
+ log(log_ev, job=j["id"], key_id=j["key_id"], **{k: v for k, v in fields.items() if k in ("state", "error", "billed_s", "exitcode")},
+ cost_usd=round(job_cost(cur), 4), pod_id=cur["pod_id"])
+ self.release_pod(j["id"], cur["pod_id"], fields.get("error") or fields.get("state"))
+ return cur
+
+ # -- watchdog: lost heartbeats -> pod_lost; launching too long -> no_capacity (neither is billed)
+ def watchdog_once(self):
+ cutoff = time.time() - HEARTBEAT_LOST_S
+ for j in self.db.q("select * from jobs where state='running' and last_hb is not null and last_hb < ?", cutoff):
+ self.finish_job(j, "job.pod_lost", state="failed", finished=now(), error="pod_lost", token_hash=None)
+ lcut = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(time.time() - self.cfg.launch_timeout_s))
+ for j in self.db.q("select * from jobs where state='launching' and last_hb is null and launched_at is not null and launched_at < ?", lcut):
+ self.finish_job(j, "job.launch_timeout", state="failed", finished=now(), error="no_capacity", token_hash=None)
+
+ # -- reaper (CONTRACT "Pod lifecycle", GJOB-099): a pod exists only while a job is launching/running
+ def reaper_once(self):
+ if not self.launcher: return 0
+ try: pods = self.launcher.list_pods()
+ except Exception as e: log("reaper.list_failed", error=repr(e)); return 0
+ self.last_reap = time.time(); n = 0
+ for p in pods:
+ pid = p.get("id"); name = p.get("name") or ""
+ if not pid or not name.startswith(POD_PREFIX): continue
+ j = self.db.job_by_pod(pid) or self.db.job(name[len(POD_PREFIX):])
+ reason = None
+ if not j: reason = "no_job"
+ elif j["state"] in TERMINAL: reason = "job_terminal"
+ else:
+ age = pod_age_s(p); wall = int((json.loads(j["spec"]).get("wall_limit_s")) or 86400)
+ if age is not None and age > wall + REAPER_GRACE_S: reason = "overage"
+ if not reason: self.reaper_fail.pop(pid, None); continue
+ try:
+ self.launcher.delete(pid); self.reaper_fail.pop(pid, None); n += 1
+ log("reaper.deleted", pod_id=pid, name=name, job=j["id"] if j else None, reason=reason, status=p.get("status"))
+ except Exception as e:
+ k = self.reaper_fail[pid] = self.reaper_fail.get(pid, 0) + 1
+ log("reaper.delete_failed", pod_id=pid, name=name, reason=reason, failures=k, error=repr(e))
+ if k >= 3: log("reaper.stuck", pod_id=pid, name=name, reason=reason, failures=k)
+ return n
+
+ def watchdog_loop(self, stop):
+ while not stop.wait(30):
+ try: self.watchdog_once(); self.purge_pending()
+ except Exception as e: log("watchdog.error", error=repr(e))
+ if self.launcher and time.time() - self.last_reap >= self.cfg.reaper_interval_s:
+ try: self.reaper_once()
+ except Exception as e: log("reaper.error", error=repr(e))
+
+# ----------------------------------------------------------------------------- HTTP
+
+PAGE = """
+{brand}{brand}
{body}"""
+
+def page(brand, body): return PAGE.format(brand=brand, body=body).encode()
+def esc(s): return str(s).replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """)
+
+class Handler(BaseHTTPRequestHandler):
+ app: App
+ server_version = "mde-endpoint/" + VERSION; sys_version = ""
+ def log_message(self, *a): pass
+ def send(self, code, obj=None, raw=None, ctype="application/json"):
+ body = raw if raw is not None else (json.dumps(obj).encode() if obj is not None else b"")
+ self.send_response(code); self.send_header("Content-Type", ctype); self.send_header("Content-Length", str(len(body)))
+ self.send_header("Cache-Control", "no-store"); self.end_headers(); self.wfile.write(body)
+ def html(self, code, body): return self.send(code, raw=page(self.app.cfg.brand, body), ctype="text/html; charset=utf-8")
+ def body(self):
+ n = int(self.headers.get("Content-Length") or 0); return self.rfile.read(n) if n else b""
+ def bearer(self):
+ a = self.headers.get("Authorization", ""); return a[7:].strip() if a.startswith("Bearer ") else None
+ def api_key(self):
+ b = self.bearer()
+ if not b or not b.startswith("mde_"): return None
+ return self.app.db.key_by_hash(sha256(b))
+ def pod_job(self, jid):
+ j = self.app.db.job(jid); b = self.bearer()
+ if not j or not b or not j["token_hash"] or not hmac.compare_digest(j["token_hash"], sha256(b)): return None
+ return j
+ def json_body(self):
+ try: return json.loads(self.body() or b"{}")
+ except json.JSONDecodeError: return None
+
+ # ------------------------------------------------------------------ GET
+ def do_GET(self):
+ u = urlparse(self.path); p = u.path.rstrip("/") or "/"; parts = p.split("/")
+ db = self.app.db
+ if p == "/v1/health":
+ return self.send(200, {"ok": True, "version": VERSION, "runners": "open" if self.app.cfg.runners_open else "closed",
+ "launcher": self.app.launcher.kind if self.app.launcher else "none"})
+ if p == "/welcome": return self.welcome(parse_qs(u.query))
+ if parts[1] == "blob" and len(parts) == 3:
+ name = parts[2]
+ if not self.app.blob_ok(name, u.query): return self.send(403, {"error": "bad or expired blob url"})
+ f = self.app.blob_path(name)
+ if not os.path.exists(f): return self.send(404, {"error": "no blob"})
+ self.send_response(200); self.send_header("Content-Type", "application/gzip"); self.send_header("Content-Length", str(os.path.getsize(f))); self.end_headers()
+ with open(f, "rb") as fh:
+ while chunk := fh.read(1 << 20): self.wfile.write(chunk)
+ return
+ if parts[1] == "internal": # pod side
+ j = self.pod_job(parts[3]) if len(parts) == 4 and parts[2] == "jobs" else None
+ if not j: return self.send(401, {"error": "bad token"})
+ spec = json.loads(j["spec"])
+ in_url, _ = self.app.blob_url(f"{j['id']}.in.tar.gz"); out_url, _ = self.app.blob_url(f"{j['id']}.out.tar.gz")
+ return self.send(200, {"input_url": in_url, "input": spec["input"], "launch": spec.get("launch", "default"),
+ "wall_limit_s": spec.get("wall_limit_s", 86400), "results_put_url": out_url})
+ if parts[1] == "v1" and len(parts) > 2 and parts[2] == "admin": return self.admin(parts[3:])
+ k = self.api_key()
+ if not k: return self.send(401, {"error": "bad api key"})
+ if p == "/v1/me":
+ return self.send(200, {"balance_usd": db.balance(k["key_id"]), "rate_table": self.app.cfg.rates, "keys_created": k["created"], "key_id": k["key_id"]})
+ if p == "/v1/jobs":
+ q = parse_qs(u.query); limit = max(1, min(int((q.get("limit") or ["50"])[0] or 50), 200)); cursor = (q.get("cursor") or [None])[0]
+ if cursor and (cj := db.job(cursor)) and cj["key_id"] == k["key_id"]:
+ rows = db.q("select * from jobs where key_id=? and (created or (created=? and id)) order by created desc, id desc limit ?", k["key_id"], cj["created"], cj["created"], cj["id"], limit)
+ else:
+ rows = db.q("select * from jobs where key_id=? order by created desc, id desc limit ?", k["key_id"], limit)
+ return self.send(200, {"jobs": [status(j) for j in rows], "next_cursor": rows[-1]["id"] if len(rows) == limit else None})
+ if len(parts) >= 4 and parts[1] == "v1" and parts[2] == "jobs":
+ j = db.job(parts[3])
+ if not j or j["key_id"] != k["key_id"]: return self.send(404, {"error": "no job"})
+ if len(parts) == 4: return self.send(200, status(j))
+ if parts[4] == "results":
+ if j["state"] not in ("done", "failed"): return self.send(409, {"error": "not finished"})
+ f = self.app.blob_path(f"{j['id']}.out.tar.gz"); sz = os.path.getsize(f) if os.path.exists(f) else 0
+ url, exp = self.app.blob_url(f"{j['id']}.out.tar.gz")
+ return self.send(200, {"download_url": url, "expires": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(exp)), "bytes": sz})
+ self.send(404, {"error": "no route"})
+
+ def admin(self, parts):
+ tok = self.app.cfg.admin_token
+ if not tok or not self.bearer() or not hmac.compare_digest(tok, self.bearer()): return self.send(403, {"error": "forbidden"})
+ db = self.app.db
+ if parts == ["stats"]:
+ by_state = {r["state"]: r["n"] for r in db.q("select state, count(*) n from jobs group by state")}
+ return self.send(200, {"keys": db.one("select count(*) n from keys")["n"], "credits_usd": db.one("select coalesce(sum(usd),0) s from credits")["s"],
+ "credit_rows": db.one("select count(*) n from credits")["n"], "jobs": by_state, "pending_keys": db.one("select count(*) n from pending_keys")["n"]})
+ return self.send(404, {"error": "no route"})
+
+ def welcome(self, q):
+ sid = (q.get("session_id") or [""])[0].strip()
+ if not sid or len(sid) > 200: return self.html(400, "Missing session
This page is reached from the payment confirmation link.
")
+ if not self.app.cfg.stripe_secret: log("welcome.no_stripe_key", session=sid); return self.html(503, "Not configured
Payment lookup is unavailable right now. Your payment is safe; try again shortly.
")
+ try:
+ sess = stripe_fetch_session(sid, self.app.cfg.stripe_secret)
+ except urllib.error.HTTPError as e:
+ log("welcome.stripe_http", session=sid, code=e.code); return self.html(404 if e.code == 404 else 502, "Session not found
We could not find that payment session. If you were charged, reply to your Stripe receipt.
")
+ except Exception as e:
+ log("welcome.stripe_error", session=sid, error=repr(e)); return self.html(502, "Temporary problem
Could not reach the payment provider. Reload in a minute; your payment is safe.
")
+ if sess.get("payment_status") != "paid":
+ return self.html(402, "Payment not completed
Stripe reports this session as %s. Once it is paid, reload this page.
" % esc(sess.get("payment_status")))
+ try: g = self.app.grant(sess, "welcome")
+ except Exception as e:
+ log("welcome.grant_error", session=sid, error=repr(e)); return self.html(500, "Something went wrong
Your payment is recorded; reply to your Stripe receipt and we will fix it.
")
+ bal = "$%.2f" % g["balance"]; hours = ("%.4g" % g["gpu_h"])
+ if g["full_key"]:
+ body = f"""Your MDEngine key
Credited {hours} GPU-hours (${g['usd']:.2f}). Balance {bal}
+Shown once. Copy it now; it is stored hashed and cannot be displayed again.
{esc(g['full_key'])}
+
Key id {esc(g['key_id'])} (this short id is what appears in support and top-ups).
+Command line
mdengine login {esc(g['full_key'])}
+mdengine run --gpu in.lmp
+
App
- MDEngine ▸ Settings ▸ Accelerated (API key)
- Paste the key
- File ▸ Run Accelerated… (⇧⌘R)
+
MCP: submit_lammps host=cloud once the key is saved by mdengine login.
+Credits never expire. Check the balance any time with mdengine account.
"""
+ return self.html(200, body)
+ if g["kind"] == "topup":
+ return self.html(200, f"""Credits added
Credited {hours} GPU-hours (${g['usd']:.2f}) to your existing key {esc(g['key_id'])}.
+New balance {bal}
mdengine account shows the same figure.
""")
+ return self.html(200, f"""Already issued
This purchase was already credited to key {esc(g['key_id'])}; balance {bal}.
+The full key is shown only once, right after payment. If you did not save it, mdengine account works if you already logged in; otherwise reply to your Stripe receipt and quote the key id above and we will issue a replacement.
""")
+
+ # ------------------------------------------------------------------ PUT (blob upload)
+ def do_PUT(self):
+ u = urlparse(self.path); parts = u.path.split("/")
+ if len(parts) != 3 or parts[1] != "blob": return self.send(404, {"error": "no route"})
+ name = parts[2]
+ if not self.app.blob_ok(name, u.query): return self.send(403, {"error": "bad or expired blob url"})
+ n = int(self.headers.get("Content-Length") or 0)
+ if n > MAX_BLOB: return self.send(413, {"error": "tarball exceeds 2 GB"})
+ jid = name.split(".")[0]; j = self.app.db.job(jid)
+ if not j: return self.send(404, {"error": "no job"})
+ if name.endswith(".in.tar.gz") and j["state"] != "created": return self.send(409, {"error": f"state is {j['state']}"})
+ tmp = self.app.blob_path(name + ".part"); left = n
+ with open(tmp, "wb") as fh:
+ while left > 0:
+ chunk = self.rfile.read(min(left, 1 << 20))
+ if not chunk: break
+ fh.write(chunk); left -= len(chunk)
+ if left: os.remove(tmp); return self.send(400, {"error": "short body"})
+ os.replace(tmp, self.app.blob_path(name))
+ if name.endswith(".in.tar.gz"): self.app.db.set_job(jid, state="uploaded")
+ log("blob.put", job=jid, name=name, bytes=n)
+ self.send(200, {"ok": True})
+
+ # ------------------------------------------------------------------ POST
+ def do_POST(self):
+ p = urlparse(self.path).path.rstrip("/"); parts = p.split("/"); db = self.app.db
+ if p == "/v1/stripe/webhook": return self.webhook()
+ if parts[1] == "internal": # pod side: heartbeat / done
+ j = self.pod_job(parts[3]) if len(parts) == 5 and parts[2] == "jobs" else None
+ if not j: return self.send(401, {"error": "bad token"})
+ b = self.json_body()
+ if b is None: return self.send(400, {"error": "bad json"})
+ if parts[4] == "heartbeat":
+ kw = {"thermo": json.dumps([str(x) for x in (b.get("thermo_tail") or [])][-20:]), "last_hb": time.time()}
+ if j["state"] in ("queued", "launching"): kw.update(state="running", started=now()); log("job.running", job=j["id"])
+ db.set_job(j["id"], **kw); return self.send(200, {"ok": True})
+ if parts[4] == "done":
+ if j["state"] in TERMINAL: return self.send(200, {"ok": True, "state": j["state"]})
+ rc = int(b.get("exitcode", 1)); err = b.get("error")
+ started = j["started"] or now(); billed = int(b.get("elapsed_s", 0))
+ if j["started"]: billed = min(billed, int(time.time() - parse_ts(j["started"])) + 60) # pod cannot bill more than wall time
+ have = os.path.exists(self.app.blob_path(f"{j['id']}.out.tar.gz"))
+ state = "done" if rc == 0 and have else "failed"
+ if not have and not err: err = "no_results"
+ self.app.finish_job(j, "job.finished", state=state, finished=now(), started=started, exitcode=rc, error=err, billed_s=billed, token_hash=None)
+ return self.send(200, {"ok": True, "state": state})
+ return self.send(404, {"error": "no route"})
+ k = self.api_key()
+ if not k: return self.send(401, {"error": "bad api key"})
+ if p == "/v1/jobs":
+ spec = self.json_body()
+ if spec is None or "input" not in spec: return self.send(400, {"error": "input required"})
+ gpu = spec.get("gpu", "any"); rate = self.app.cfg.rates.get(gpu)
+ if rate is None: return self.send(400, {"error": "unknown gpu"})
+ try: est = int(spec.get("estimate_s", 0)); wall = int(spec.get("wall_limit_s", 14400))
+ except (TypeError, ValueError): return self.send(400, {"error": "bad numbers"})
+ if wall > 86400 or wall <= 0: return self.send(400, {"error": "wall_limit_s must be 1..86400"})
+ if len(str(spec.get("label") or "")) > 120: return self.send(400, {"error": "label too long"})
+ if db.balance(k["key_id"]) < rate * max(est, 900) / 3600: return self.send(402, {"error": "insufficient balance"})
+ if not self.app.cfg.runners_open:
+ log("job.refused_closed", key_id=k["key_id"])
+ return self.send(503, {"error": "gpu_runners_open_soon", "message": "GPU runners open this week; your credits are safe and never expire."})
+ jid = job_id()
+ spec["wall_limit_s"] = wall
+ db.x("insert into jobs(id,key_id,spec,state,created,gpu,rate) values(?,?,?,?,?,?,?)",
+ jid, k["key_id"], json.dumps(spec), "created", now(), gpu, rate)
+ url, exp = self.app.blob_url(f"{jid}.in.tar.gz", ttl=3600)
+ log("job.created", job=jid, key_id=k["key_id"], gpu=gpu)
+ return self.send(201, {"id": jid, "upload_url": url, "upload_expires": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(exp))})
+ if len(parts) == 5 and parts[1] == "v1" and parts[2] == "jobs" and parts[4] == "start":
+ j = db.job(parts[3])
+ if not j or j["key_id"] != k["key_id"]: return self.send(404, {"error": "no job"})
+ if j["state"] != "uploaded": return self.send(409, {"error": f"state is {j['state']}"})
+ tok = "jt_" + secrets.token_hex(16) # minted here, stored hashed, handed to the pod only
+ db.set_job(j["id"], state="queued", token_hash=sha256(tok))
+ spec = json.loads(j["spec"]); wall = int(spec.get("wall_limit_s") or 86400)
+ if self.app.launcher: # background: queued -> launching | failed:no_capacity
+ self.app.spawn(self.app.launch_job, j["id"], tok, wall, j["gpu"])
+ else: # dev path: launch env on disk for a hand-run pod
+ with open(self.app.launch_env, "w", opener=lambda f, fl: os.open(f, fl, 0o600)) as fh:
+ fh.write(f"MDE_ENDPOINT={self.app.public_url}\nMDE_JOB_ID={j['id']}\nMDE_JOB_TOKEN={tok}\n")
+ log("job.queued", job=j["id"], key_id=k["key_id"], launcher=bool(self.app.launcher))
+ return self.send(202, {"id": j["id"], "state": "queued"})
+ self.send(404, {"error": "no route"})
+
+ def webhook(self):
+ payload = self.body(); cfg = self.app.cfg
+ if not verify_stripe_signature(payload, self.headers.get("Stripe-Signature", ""), cfg.webhook_secret):
+ log("webhook.bad_signature"); return self.send(400, {"error": "bad signature"})
+ try: event = json.loads(payload)
+ except json.JSONDecodeError: return self.send(400, {"error": "bad json"})
+ etype = event.get("type"); obj = (event.get("data") or {}).get("object") or {}
+ if etype != "checkout.session.completed": return self.send(200, {"ok": True, "ignored": etype})
+ sid = obj.get("id")
+ if not sid: return self.send(400, {"error": "no session id"})
+ sess = obj
+ if cfg.stripe_secret: # webhook payloads carry no line_items; fetch to learn the price id
+ try: sess = stripe_fetch_session(sid, cfg.stripe_secret)
+ except Exception as e: log("webhook.stripe_fetch_failed", session=sid, error=repr(e))
+ if sess.get("payment_status") != "paid": return self.send(200, {"ok": True, "ignored": "unpaid"})
+ try: g = self.app.grant(sess, "webhook")
+ except Exception as e:
+ log("webhook.grant_error", session=sid, error=repr(e)); return self.send(500, {"error": "grant failed"})
+ return self.send(200, {"ok": True, "result": g["kind"], "key_id": g["key_id"]})
+
+ # ------------------------------------------------------------------ DELETE (cancel)
+ def do_DELETE(self):
+ parts = urlparse(self.path).path.rstrip("/").split("/"); db = self.app.db
+ k = self.api_key()
+ if not k: return self.send(401, {"error": "bad api key"})
+ if len(parts) != 4 or parts[1] != "v1" or parts[2] != "jobs": return self.send(404, {"error": "no route"})
+ j = db.job(parts[3])
+ if not j or j["key_id"] != k["key_id"]: return self.send(404, {"error": "no job"})
+ if j["state"] in TERMINAL: return self.send(409, {"error": "terminal"})
+ billed = billed_seconds(j) if j["state"] == "running" else 0
+ cur = self.app.finish_job(j, "job.cancelled", state="cancelled", finished=now(), error="cancelled", billed_s=billed, token_hash=None)
+ self.send(202, status(cur))
+
+# ----------------------------------------------------------------------------- server
+
+def make_server(cfg: Config):
+ """Bind and return (server, app). Port 0 in MDE_BIND picks a free port (tests)."""
+ app = App(cfg)
+ handler = type("BoundHandler", (Handler,), {"app": app})
+ host, _, port = cfg.bind.rpartition(":")
+ srv = ThreadingHTTPServer((host or "127.0.0.1", int(port or 8080)), handler)
+ srv.daemon_threads = True
+ if not cfg.public_url: app.public_url = "http://%s:%d" % srv.server_address[:2]
+ if app.launcher: app.launcher.public_url = app.public_url
+ return srv, app
+
+def main(argv=None):
+ ap = argparse.ArgumentParser(description=__doc__.split("\n")[0])
+ ap.add_argument("--env-file", default=os.environ.get("MDE_ENV_FILE"), help="KEY=VALUE file; existing env wins")
+ a = ap.parse_args(argv); load_env_file(a.env_file)
+ cfg = Config(); srv, app = make_server(cfg)
+ log("start", version=VERSION, bind=cfg.bind, public_url=app.public_url, runners="open" if cfg.runners_open else "closed",
+ packs=len(cfg.packs), stripe=bool(cfg.stripe_secret), webhook=bool(cfg.webhook_secret), db=cfg.db,
+ launcher=app.launcher.kind if app.launcher else "none", ladder=cfg.gpu_ladder if app.launcher else None)
+ if app.launcher: # boot-time reap: an endpoint outage must not leave orphans behind it
+ try: log("reaper.boot", deleted=app.reaper_once())
+ except Exception as e: log("reaper.error", error=repr(e))
+ stop = threading.Event(); threading.Thread(target=app.watchdog_loop, args=(stop,), daemon=True).start()
+ try: srv.serve_forever()
+ except KeyboardInterrupt: pass
+ finally: stop.set(); srv.server_close(); log("stop")
+
+if __name__ == "__main__":
+ main()
diff --git a/hosted/endpoint/mde_launcher.py b/hosted/endpoint/mde_launcher.py
new file mode 100644
index 0000000..7634b1b
--- /dev/null
+++ b/hosted/endpoint/mde_launcher.py
@@ -0,0 +1,186 @@
+#!/usr/bin/env python3
+"""RunPod pod launcher for the MDEngine hosted endpoint (hosted/CONTRACT.md "Pod lifecycle"). Stdlib only.
+
+One pod per job. The pod is the runner image with MDE_ENDPOINT / MDE_JOB_ID / MDE_JOB_TOKEN in its env;
+docker/runner-gpu/start.sh execs runner.sh when MDE_JOB_ID is set. The endpoint deletes the pod at every
+terminal job state and the reaper (mde_endpoint.App.reaper_once) clears anything that slipped through.
+
+RunPod REST v2 (https://api.runpod.io/v2/openapi.json):
+ POST /v2/pods 201 Pod create; any other status = this ladder rung failed
+ GET /v2/pods/{id} 200 Pod status in PROVISIONING STARTING RUNNING EXITED ERROR TERMINATED
+ DELETE /v2/pods/{id} 204 (404 = gone) idempotent
+ GET /v2/pods 200 {"pods":[Pod]} (live) | {"items":[Pod]} (docs) | bare list
+
+Logging discipline: the API key is only ever a header; request bodies (they carry the job token) are never
+logged; Pod objects returned by RunPod carry the pod env (job token) and are never logged either -- only
+error bodies, truncated to LOG_BODY_MAX.
+"""
+import json, threading, time, urllib.error, urllib.request
+from datetime import datetime, timezone
+
+RUNPOD_BASE = "https://api.runpod.io"
+DEFAULT_IMAGE = "ghcr.io/forcefieldsilicon/mdengine-runner-gpu:ADA89"
+DEFAULT_LADDER = [("COMMUNITY", "NVIDIA GeForce RTX 4090"), ("SECURE", "NVIDIA GeForce RTX 4090")]
+CLOUDS = ("COMMUNITY", "SECURE")
+HTTP_TIMEOUT_S = 30
+LOG_BODY_MAX = 300
+POD_NAME_PREFIX = "mde-"
+
+class LauncherError(Exception):
+ """Transport or protocol failure talking to RunPod (never carries the API key or a token)."""
+
+class NoCapacity(LauncherError):
+ """Every rung of the fallback ladder refused to create a pod."""
+
+def log(ev, **kw):
+ rec = {"ts": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "ev": ev}; rec.update(kw)
+ print(json.dumps(rec, separators=(",", ":"), default=str), flush=True)
+
+def trunc(s, n=LOG_BODY_MAX):
+ s = s if isinstance(s, str) else str(s)
+ return s if len(s) <= n else s[:n] + "...(%d more)" % (len(s) - n)
+
+def parse_ladder(s):
+ """MDE_GPU_LADDER="COMMUNITY:NVIDIA GeForce RTX 4090,SECURE:NVIDIA GeForce RTX 4090" -> [(cloud, gpu_id)]."""
+ if not (s or "").strip(): return list(DEFAULT_LADDER)
+ out = []
+ for item in s.split(","):
+ item = item.strip()
+ if not item: continue
+ if ":" not in item: raise ValueError("MDE_GPU_LADDER entry needs CLOUD:gpu id, got %r" % item)
+ cloud, gid = item.split(":", 1); cloud = cloud.strip().upper(); gid = gid.strip()
+ if cloud not in CLOUDS or not gid: raise ValueError("bad MDE_GPU_LADDER entry %r" % item)
+ out.append((cloud, gid))
+ if not out: raise ValueError("MDE_GPU_LADDER is empty")
+ return out
+
+def parse_iso(s):
+ """RunPod createdAt ('2026-09-06T12:34:56.789Z' or with an offset) -> unix seconds, or None."""
+ if not s: return None
+ try:
+ if s.endswith("Z"): s = s[:-1] + "+00:00"
+ d = datetime.fromisoformat(s)
+ if d.tzinfo is None: d = d.replace(tzinfo=timezone.utc)
+ return d.timestamp()
+ except ValueError: return None
+
+def pod_age_s(pod, now_ts=None):
+ t = parse_iso((pod or {}).get("createdAt")); return None if t is None else max(0.0, (now_ts or time.time()) - t)
+
+def iso_now(ts=None): return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(ts))
+
+# ----------------------------------------------------------------------------- RunPod
+
+class RunPodLauncher:
+ kind = "runpod"
+ def __init__(self, cfg, public_url=None):
+ self.api_key = cfg.runpod_api_key
+ if not self.api_key: raise ValueError("RUNPOD_API_KEY is empty")
+ self.image = getattr(cfg, "runner_image", "") or DEFAULT_IMAGE
+ self.disk_gb = int(getattr(cfg, "pod_disk_gb", 20) or 20)
+ self.min_cuda = getattr(cfg, "min_cuda", "") or "12.4"
+ self.ladder = list(getattr(cfg, "gpu_ladder", None) or DEFAULT_LADDER)
+ self.public_url = (public_url or cfg.public_url or "").rstrip("/")
+ self.base = getattr(cfg, "runpod_base", "") or RUNPOD_BASE
+ self.timeout = HTTP_TIMEOUT_S
+ self.backoff_s = 1.0 # delete() retry base; tests set 0
+ self._lock = threading.Lock() # serialises pod creation (one launch thread at a time is plenty)
+
+ # -- transport (split so tests can monkeypatch either layer) ------------------------------------
+ def _open(self, req):
+ """urlopen -> (status, text). HTTP errors are returned, not raised; transport errors raise LauncherError."""
+ try:
+ with urllib.request.urlopen(req, timeout=self.timeout) as r: return r.status, r.read().decode(errors="replace")
+ except urllib.error.HTTPError as e:
+ with e: return e.code, e.read().decode(errors="replace")
+ except (urllib.error.URLError, OSError, TimeoutError) as e:
+ raise LauncherError("runpod transport: %s" % trunc(repr(e), 120)) from None
+
+ def _request(self, method, path, body=None):
+ """(status, text) for METHOD {base}{path}. Authorization is set here and nowhere else."""
+ data = json.dumps(body).encode() if body is not None else None
+ req = urllib.request.Request(self.base + path, data=data, method=method,
+ headers={"Authorization": "Bearer " + self.api_key, "Accept": "application/json",
+ "User-Agent": "mde-endpoint-launcher"})
+ if data is not None: req.add_header("Content-Type", "application/json")
+ return self._open(req)
+
+ # -- interface ---------------------------------------------------------------------------------
+ def pod_body(self, job_id, token, cloud, gpu_id, wall_limit_s=86400):
+ return {"name": POD_NAME_PREFIX + job_id, "image": self.image, "cloud": cloud,
+ "gpu": {"id": gpu_id, "count": 1, "minCudaVersion": self.min_cuda}, "disk": self.disk_gb,
+ "env": {"MDE_ENDPOINT": self.public_url, "MDE_JOB_ID": job_id, "MDE_JOB_TOKEN": token,
+ "MDE_WALL_LIMIT_S": str(int(wall_limit_s))}} # pod-side TTL (start.sh): wall + 600 s
+
+ def create(self, job_id, token, wall_limit_s, gpu):
+ """Walk the ladder until one POST /v2/pods returns 201; return the pod id. Raises NoCapacity."""
+ with self._lock:
+ for rung, (cloud, gpu_id) in enumerate(self.ladder, 1):
+ try: code, text = self._request("POST", "/v2/pods", self.pod_body(job_id, token, cloud, gpu_id, wall_limit_s))
+ except LauncherError as e: code, text = 0, str(e)
+ pod_id = None
+ if code == 201: # a Pod body: carries env, so never logged
+ try: pod_id = json.loads(text).get("id")
+ except (ValueError, AttributeError): pod_id = None
+ text = "<201 without pod id>"
+ log("launch.attempt", job=job_id, rung=rung, cloud=cloud, gpu_id=gpu_id, gpu=gpu, wall_limit_s=wall_limit_s,
+ code=code, ok=bool(pod_id), **({} if pod_id else {"body": trunc(text)}))
+ if pod_id: return pod_id
+ raise NoCapacity("no rung of %d accepted job %s" % (len(self.ladder), job_id))
+
+ def delete(self, pod_id, retries=3):
+ """DELETE /v2/pods/{id}; 204 and 404 are success. 429/5xx/transport errors retry with backoff."""
+ last = None
+ for i in range(retries):
+ try: code, text = self._request("DELETE", "/v2/pods/" + pod_id)
+ except LauncherError as e: code, text = 0, str(e)
+ if code in (200, 202, 204, 404): return True
+ last = "%d %s" % (code, trunc(text))
+ if code and code != 429 and code < 500: break # other 4xx: not retryable
+ if i + 1 < retries: time.sleep(self.backoff_s * (2 ** i))
+ raise LauncherError("delete %s failed: %s" % (pod_id, last))
+
+ def get(self, pod_id):
+ code, text = self._request("GET", "/v2/pods/" + pod_id)
+ if code == 404: return None
+ if code != 200: raise LauncherError("get %s: %d %s" % (pod_id, code, trunc(text)))
+ return json.loads(text)
+
+ def list_pods(self):
+ code, text = self._request("GET", "/v2/pods")
+ if code != 200: raise LauncherError("list pods: %d %s" % (code, trunc(text)))
+ data = json.loads(text)
+ # live API (2026-09) wraps as {"pods":[...]}; OpenAPI example says {"items":[...]}; older: bare list
+ items = (data.get("pods") or data.get("items")) if isinstance(data, dict) else data
+ return list(items or [])
+
+# ----------------------------------------------------------------------------- fake (tests / dry runs)
+
+class FakeLauncher:
+ """Same interface, in memory. `fail_create=True` raises NoCapacity; pod ids in `fail_delete` refuse deletion."""
+ kind = "fake"
+ def __init__(self, public_url=""):
+ self.public_url = public_url; self.pods = {}; self.deleted = []; self.calls = []
+ self.fail_create = False; self.fail_delete = set(); self._n = 0; self._lock = threading.Lock()
+
+ def add_pod(self, name, created_at=None, pod_id=None, env=None):
+ with self._lock:
+ self._n += 1; pid = pod_id or "fakepod%d" % self._n
+ self.pods[pid] = {"id": pid, "name": name, "status": "RUNNING", "cloud": "COMMUNITY",
+ "createdAt": created_at or iso_now(), "env": dict(env or {})}
+ return pid
+
+ def create(self, job_id, token, wall_limit_s, gpu):
+ self.calls.append(("create", job_id))
+ log("launch.attempt", job=job_id, rung=1, cloud="FAKE", gpu_id="fake", gpu=gpu, wall_limit_s=wall_limit_s, ok=not self.fail_create)
+ if self.fail_create: raise NoCapacity("fake: no capacity")
+ return self.add_pod(POD_NAME_PREFIX + job_id, env={"MDE_ENDPOINT": self.public_url, "MDE_JOB_ID": job_id, "MDE_JOB_TOKEN": token})
+
+ def delete(self, pod_id):
+ self.calls.append(("delete", pod_id))
+ if pod_id in self.fail_delete: raise LauncherError("fake: delete refused for %s" % pod_id)
+ with self._lock: self.pods.pop(pod_id, None); self.deleted.append(pod_id)
+ return True
+
+ def get(self, pod_id): return self.pods.get(pod_id)
+ def list_pods(self): return [dict(p) for p in self.pods.values()]
diff --git a/hosted/endpoint/test_endpoint.py b/hosted/endpoint/test_endpoint.py
new file mode 100644
index 0000000..e14ebe3
--- /dev/null
+++ b/hosted/endpoint/test_endpoint.py
@@ -0,0 +1,477 @@
+#!/usr/bin/env python3
+"""Tests for mde_endpoint.py / mde_admin.py. Stdlib unittest; starts the server on a random port with a
+temp db and a fake Stripe (stripe_fetch_session monkeypatched). No network beyond 127.0.0.1.
+
+ python3 hosted/endpoint/test_endpoint.py -v
+"""
+import io, json, os, re, shutil, sys, tempfile, threading, time, unittest, urllib.error, urllib.request
+from contextlib import redirect_stdout
+
+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
+import mde_endpoint as E
+import mde_admin as A
+import mde_launcher as L
+
+PRICE_STARTER, PRICE_LAB = "price_test_starter", "price_test_lab"
+WEBHOOK_SECRET = "whsec_test_" + "a" * 20
+
+FAKE_SESSIONS = {}
+
+def fake_session(sid, price=PRICE_STARTER, paid=True, amount=2500, ref=None, email="buyer@example.test"):
+ s = {"id": sid, "object": "checkout.session", "payment_status": "paid" if paid else "unpaid", "amount_total": amount,
+ "client_reference_id": ref, "customer_details": {"email": email},
+ "line_items": {"data": [{"price": {"id": price}}]}}
+ FAKE_SESSIONS[sid] = s; return s
+
+def fake_fetch(sid, secret):
+ if sid not in FAKE_SESSIONS: raise urllib.error.HTTPError("https://api.stripe.com", 404, "no such session", {}, None)
+ return FAKE_SESSIONS[sid]
+
+class Server:
+ def __init__(self, runners_open=False, launcher=None):
+ self.dir = tempfile.mkdtemp(prefix="mde-test-")
+ env = {"MDE_DB": os.path.join(self.dir, "t.sqlite"), "MDE_BLOBS": os.path.join(self.dir, "blobs"), "MDE_BIND": "127.0.0.1:0",
+ "STRIPE_SECRET_KEY": "sk_test_fake", "STRIPE_WEBHOOK_SECRET": WEBHOOK_SECRET,
+ "MDE_PACKS": f"{PRICE_STARTER}:25:12.5,{PRICE_LAB}:100:50", "MDE_RUNNERS_OPEN": "1" if runners_open else "",
+ "MDE_ADMIN_TOKEN": "admintok"}
+ self.cfg = E.Config(env); self.srv, self.app = E.make_server(self.cfg)
+ self.base = self.app.public_url
+ if launcher is not None: launcher.public_url = self.base; self.app.launcher = launcher
+ self.t = threading.Thread(target=self.srv.serve_forever, daemon=True); self.t.start()
+ def close(self):
+ self.srv.shutdown(); self.srv.server_close(); self.app.db.c.close(); shutil.rmtree(self.dir, ignore_errors=True)
+
+ def req(self, method, path, body=None, key=None, headers=None, raw=None):
+ data = raw if raw is not None else (json.dumps(body).encode() if body is not None else None)
+ url = path if path.startswith("http") else self.base + path
+ r = urllib.request.Request(url, data=data, method=method)
+ if key: r.add_header("Authorization", "Bearer " + key)
+ if body is not None: r.add_header("Content-Type", "application/json")
+ for k, v in (headers or {}).items(): r.add_header(k, v)
+ try:
+ with urllib.request.urlopen(r, timeout=10) as resp: return resp.status, resp.read(), resp.headers
+ except urllib.error.HTTPError as e:
+ with e: return e.code, e.read(), e.headers
+
+ def js(self, *a, **kw):
+ code, body, _ = self.req(*a, **kw); return code, json.loads(body)
+
+ def admin_key(self, credit, email="op@example.test"):
+ """Create a key via the admin CLI against the same sqlite file; returns (full_key, key_id)."""
+ out = io.StringIO()
+ with redirect_stdout(out): A.main(["--db", self.cfg.db, "--env-file", "/nonexistent", "key", "new", "--email", email, "--credit", str(credit)])
+ full = re.search(r"\b(mde_[0-9a-f]{32})\b", out.getvalue()).group(1)
+ kid = re.search(r"key_id\s+(\w+)", out.getvalue()).group(1)
+ return full, kid
+
+class Base(unittest.TestCase):
+ runners_open = False
+ fake_launcher = False
+ def setUp(self):
+ self._orig = E.stripe_fetch_session; E.stripe_fetch_session = fake_fetch; FAKE_SESSIONS.clear()
+ self.fake = L.FakeLauncher() if self.fake_launcher else None
+ self.s = Server(self.runners_open, launcher=self.fake); self._log = io.StringIO(); self._logpatch = redirect_stdout(self._log); self._logpatch.__enter__()
+ def tearDown(self):
+ self._logpatch.__exit__(None, None, None); self.s.close(); E.stripe_fetch_session = self._orig
+
+class TestAuthAndBalance(Base):
+ def test_health(self):
+ code, j = self.s.js("GET", "/v1/health")
+ self.assertEqual(code, 200); self.assertTrue(j["ok"]); self.assertEqual(j["runners"], "closed"); self.assertEqual(j["version"], E.VERSION)
+
+ def test_me_and_key_hashing(self):
+ full, kid = self.s.admin_key(20)
+ code, j = self.s.js("GET", "/v1/me", key=full)
+ self.assertEqual(code, 200); self.assertEqual(j["balance_usd"], 20.0); self.assertEqual(j["key_id"], kid)
+ self.assertIn("rate_table", j); self.assertIn("keys_created", j)
+ # Only the hash is at rest.
+ row = self.s.app.db.one("select * from keys where key_id=?", kid)
+ self.assertEqual(row["key_hash"], E.sha256(full)); self.assertNotIn(full, json.dumps(dict(row)))
+ self.assertEqual(kid, E.sha256(full)[:8])
+ # Bad / missing keys.
+ self.assertEqual(self.s.js("GET", "/v1/me", key="mde_" + "0" * 32)[0], 401)
+ self.assertEqual(self.s.js("GET", "/v1/me")[0], 401)
+ # Log output never contains the full key.
+ self.assertNotIn(full, self._log.getvalue())
+
+ def test_submit_closed_503_after_auth_and_balance(self):
+ full, _ = self.s.admin_key(20)
+ spec = {"input": "in.lmp", "gpu": "any", "estimate_s": 3600, "wall_limit_s": 7200}
+ code, j = self.s.js("POST", "/v1/jobs", body=spec, key=full)
+ self.assertEqual(code, 503); self.assertEqual(j["error"], "gpu_runners_open_soon"); self.assertIn("never expire", j["message"])
+ # Auth failure wins over the flag, and so does an insufficient balance.
+ self.assertEqual(self.s.js("POST", "/v1/jobs", body=spec, key="mde_" + "f" * 32)[0], 401)
+ poor, _ = self.s.admin_key(0.10)
+ self.assertEqual(self.s.js("POST", "/v1/jobs", body=spec, key=poor)[0], 402)
+ self.assertEqual(self.s.js("GET", "/v1/jobs", key=full)[1], {"jobs": [], "next_cursor": None})
+
+class TestPurchase(Base):
+ def welcome(self, sid):
+ code, body, _ = self.s.req("GET", "/welcome?session_id=" + sid); return code, body.decode()
+
+ def test_first_purchase_creates_key(self):
+ fake_session("cs_test_1")
+ code, html = self.welcome("cs_test_1")
+ self.assertEqual(code, 200); self.assertIn("ForceField Silicon / MDEngine", html); self.assertIn("Shown once", html)
+ full = re.search(r"mdengine login (mde_[0-9a-f]{32})", html).group(1)
+ code, j = self.s.js("GET", "/v1/me", key=full)
+ self.assertEqual(code, 200); self.assertEqual(j["balance_usd"], 25.0) # 12.5 h * $2
+ c = self.s.app.db.one("select * from credits where session_id='cs_test_1'")
+ self.assertEqual(c["gpu_s"], 45000); self.assertEqual(c["price_id"], PRICE_STARTER); self.assertEqual(c["key_id"], j["key_id"])
+ self.assertEqual(self.s.app.db.key_by_id(j["key_id"])["email"], "buyer@example.test")
+ self.assertNotIn(full, self._log.getvalue())
+ # Revisit: idempotent, no key shown.
+ code, html2 = self.welcome("cs_test_1")
+ self.assertEqual(code, 200); self.assertIn("Already issued", html2); self.assertNotIn(full, html2); self.assertIn(j["key_id"], html2)
+ self.assertEqual(self.s.app.db.one("select count(*) n from credits")["n"], 1)
+ self.assertEqual(self.s.app.db.one("select count(*) n from keys")["n"], 1)
+ self.assertEqual(self.s.js("GET", "/v1/me", key=full)[1]["balance_usd"], 25.0)
+
+ def test_coupon_discount_still_grants_full_hours(self):
+ fake_session("cs_coupon", price=PRICE_LAB, amount=1) # paid $0.01 with a coupon
+ code, html = self.welcome("cs_coupon")
+ full = re.search(r"mdengine login (mde_[0-9a-f]{32})", html).group(1)
+ self.assertEqual(self.s.js("GET", "/v1/me", key=full)[1]["balance_usd"], 100.0) # 50 h * $2
+
+ def test_topup_existing_key_by_client_reference_id(self):
+ full, kid = self.s.admin_key(5)
+ fake_session("cs_topup", ref=kid)
+ code, html = self.welcome("cs_topup")
+ self.assertEqual(code, 200); self.assertIn("existing key", html); self.assertIn(kid, html); self.assertIn("$30.00", html)
+ self.assertNotRegex(html, r"mde_[0-9a-f]{32}")
+ self.assertEqual(self.s.js("GET", "/v1/me", key=full)[1]["balance_usd"], 30.0)
+ self.assertEqual(self.s.app.db.one("select count(*) n from keys")["n"], 1)
+
+ def test_unknown_reference_id_creates_new_key(self):
+ fake_session("cs_badref", ref="deadbeef")
+ code, html = self.welcome("cs_badref")
+ self.assertEqual(code, 200); self.assertRegex(html, r"mde_[0-9a-f]{32}")
+
+ def test_unpaid_and_missing_sessions(self):
+ fake_session("cs_unpaid", paid=False)
+ self.assertEqual(self.welcome("cs_unpaid")[0], 402)
+ self.assertEqual(self.welcome("cs_nope")[0], 404)
+ self.assertEqual(self.s.req("GET", "/welcome")[0], 400)
+ self.assertEqual(self.s.app.db.one("select count(*) n from credits")["n"], 0)
+
+class TestWebhook(Base):
+ def event(self, sid, etype="checkout.session.completed"):
+ return json.dumps({"id": "evt_1", "type": etype, "data": {"object": {"id": sid, "object": "checkout.session", "payment_status": "paid"}}}).encode()
+
+ def post(self, payload, sig):
+ return self.s.js("POST", "/v1/stripe/webhook", raw=payload, headers={"Stripe-Signature": sig, "Content-Type": "application/json"})
+
+ def test_signature_reject(self):
+ fake_session("cs_wh"); p = self.event("cs_wh")
+ self.assertEqual(self.post(p, "")[0], 400)
+ self.assertEqual(self.post(p, E.stripe_sign(p, "whsec_wrong"))[0], 400)
+ self.assertEqual(self.post(p, E.stripe_sign(p, WEBHOOK_SECRET, ts=time.time() - 600))[0], 400) # stale
+ self.assertEqual(self.post(p + b" ", E.stripe_sign(p, WEBHOOK_SECRET))[0], 400) # tampered body
+ self.assertEqual(self.s.app.db.one("select count(*) n from credits")["n"], 0)
+
+ def test_accept_then_welcome_reveals_once(self):
+ fake_session("cs_wh"); p = self.event("cs_wh")
+ code, j = self.post(p, E.stripe_sign(p, WEBHOOK_SECRET))
+ self.assertEqual(code, 200); self.assertEqual(j["result"], "new")
+ # Duplicate delivery: 200, nothing double-credited.
+ code, j2 = self.post(p, E.stripe_sign(p, WEBHOOK_SECRET))
+ self.assertEqual(code, 200); self.assertEqual(j2["result"], "already"); self.assertEqual(j2["key_id"], j["key_id"])
+ self.assertEqual(self.s.app.db.one("select count(*) n from credits")["n"], 1)
+ # The buyer lands on /welcome after the webhook already ran: key shown exactly once.
+ code, body, _ = self.s.req("GET", "/welcome?session_id=cs_wh"); html = body.decode()
+ self.assertEqual(code, 200); full = re.search(r"mdengine login (mde_[0-9a-f]{32})", html).group(1)
+ self.assertEqual(self.s.js("GET", "/v1/me", key=full)[1]["balance_usd"], 25.0)
+ self.assertEqual(self.s.app.db.one("select count(*) n from pending_keys")["n"], 0)
+ code, body, _ = self.s.req("GET", "/welcome?session_id=cs_wh")
+ self.assertIn("Already issued", body.decode()); self.assertNotIn(full, body.decode())
+
+ def test_welcome_then_webhook_is_idempotent(self):
+ fake_session("cs_both")
+ code, body, _ = self.s.req("GET", "/welcome?session_id=cs_both")
+ full = re.search(r"mdengine login (mde_[0-9a-f]{32})", body.decode()).group(1)
+ p = self.event("cs_both"); code, j = self.post(p, E.stripe_sign(p, WEBHOOK_SECRET))
+ self.assertEqual(code, 200); self.assertEqual(j["result"], "already")
+ self.assertEqual(self.s.js("GET", "/v1/me", key=full)[1]["balance_usd"], 25.0)
+ self.assertEqual(self.s.app.db.one("select count(*) n from pending_keys")["n"], 0)
+
+ def test_other_events_ignored(self):
+ p = self.event("cs_x", etype="payment_intent.succeeded")
+ code, j = self.post(p, E.stripe_sign(p, WEBHOOK_SECRET)); self.assertEqual(code, 200); self.assertEqual(j["ignored"], "payment_intent.succeeded")
+
+class TestAdmin(Base):
+ def run_admin(self, *args):
+ out = io.StringIO()
+ with redirect_stdout(out): A.main(["--db", self.s.cfg.db, "--env-file", "/nonexistent", *args])
+ return out.getvalue()
+
+ def test_key_new_and_credit_add(self):
+ full, kid = self.s.admin_key(10, email="a@example.test")
+ self.assertEqual(self.s.js("GET", "/v1/me", key=full)[1]["balance_usd"], 10.0)
+ out = self.run_admin("credit", "add", "--key", kid, "--usd", "15")
+ self.assertIn("balance $25.00", out)
+ self.assertEqual(self.s.js("GET", "/v1/me", key=full)[1]["balance_usd"], 25.0)
+ lst = self.run_admin("key", "list"); self.assertIn(kid, lst); self.assertIn("a@example.test", lst); self.assertNotIn(full, lst)
+ led = self.run_admin("ledger"); self.assertEqual(led.count("admin-"), 2)
+ st = self.run_admin("stats"); self.assertIn("keys 1", st); self.assertIn("credited usd 25.00", st)
+ with self.assertRaises(SystemExit): self.run_admin("credit", "add", "--key", "nokey000", "--usd", "1")
+
+ def test_admin_http_stats(self):
+ self.assertEqual(self.s.js("GET", "/v1/admin/stats")[0], 403)
+ code, j = self.s.js("GET", "/v1/admin/stats", key="admintok"); self.assertEqual(code, 200); self.assertEqual(j["keys"], 0)
+
+class TestJobFlowWhenOpen(Base):
+ """The mock's job logic, behind the flag, end to end with a stand-in pod."""
+ runners_open = True
+ def launch_token(self):
+ with open(self.s.app.launch_env) as fh: return dict(l.split("=", 1) for l in fh.read().splitlines())["MDE_JOB_TOKEN"]
+
+ def test_submit_upload_start_pod_done_billed(self):
+ full, kid = self.s.admin_key(20)
+ self.assertEqual(self.s.js("GET", "/v1/health")[1]["runners"], "open")
+ code, j = self.s.js("POST", "/v1/jobs", body={"input": "in.lmp", "gpu": "rtx4090", "estimate_s": 600, "label": "t"}, key=full)
+ self.assertEqual(code, 201); jid = j["id"]; self.assertRegex(jid, r"^MDJOB-\d{8}-[A-Z0-9]{6}$")
+ up = j["upload_url"]; self.assertIn("sig=", up)
+ # Unsigned PUT refused; signed PUT accepted; state -> uploaded.
+ self.assertEqual(self.s.req("PUT", up.split("?")[0], raw=b"x")[0], 403)
+ self.assertEqual(self.s.req("PUT", up, raw=b"deck-tarball")[0], 200)
+ self.assertEqual(self.s.js("GET", f"/v1/jobs/{jid}", key=full)[1]["state"], "uploaded")
+ code, j = self.s.js("POST", f"/v1/jobs/{jid}/start", key=full); self.assertEqual(code, 202); self.assertEqual(j["state"], "queued")
+ tok = self.launch_token(); self.assertTrue(tok.startswith("jt_"))
+ self.assertEqual(self.s.app.db.job(jid)["token_hash"], E.sha256(tok))
+ # Pod side.
+ self.assertEqual(self.s.js("GET", f"/internal/jobs/{jid}", key="jt_" + "0" * 32)[0], 401)
+ code, spec = self.s.js("GET", f"/internal/jobs/{jid}", key=tok)
+ self.assertEqual(code, 200); self.assertEqual(spec["input"], "in.lmp"); self.assertEqual(spec["launch"], "default")
+ self.assertEqual(self.s.req("GET", spec["input_url"])[1], b"deck-tarball")
+ self.assertEqual(self.s.js("POST", f"/internal/jobs/{jid}/heartbeat", body={"thermo_tail": ["Step Temp", "1 300"], "elapsed_s": 1}, key=tok)[0], 200)
+ st = self.s.js("GET", f"/v1/jobs/{jid}", key=full)[1]; self.assertEqual(st["state"], "running"); self.assertEqual(st["thermo_tail"], ["Step Temp", "1 300"])
+ self.assertEqual(self.s.req("PUT", spec["results_put_url"], raw=b"results-tarball")[0], 200)
+ code, j = self.s.js("POST", f"/internal/jobs/{jid}/done", body={"exitcode": 0, "elapsed_s": 36, "results_bytes": 15}, key=tok)
+ self.assertEqual(code, 200); self.assertEqual(j["state"], "done")
+ st = self.s.js("GET", f"/v1/jobs/{jid}", key=full)[1]
+ self.assertEqual(st["state"], "done"); self.assertEqual(st["billed_s"], 36); self.assertEqual(st["cost_usd"], 0.02); self.assertEqual(st["exitcode"], 0)
+ self.assertEqual(self.s.js("GET", "/v1/me", key=full)[1]["balance_usd"], 19.98)
+ # Token invalidated after terminal state; results fetchable.
+ self.assertEqual(self.s.js("GET", f"/internal/jobs/{jid}", key=tok)[0], 401)
+ code, r = self.s.js("GET", f"/v1/jobs/{jid}/results", key=full); self.assertEqual(code, 200); self.assertEqual(r["bytes"], 15)
+ self.assertEqual(self.s.req("GET", r["download_url"])[1], b"results-tarball")
+ lst = self.s.js("GET", "/v1/jobs", key=full)[1]; self.assertEqual([x["id"] for x in lst["jobs"]], [jid])
+ self.assertEqual(self.s.js("DELETE", f"/v1/jobs/{jid}", key=full)[0], 409)
+ # Ledger from the CLI agrees.
+ out = io.StringIO()
+ with redirect_stdout(out): A.main(["--db", self.s.cfg.db, "--env-file", "/nonexistent", "ledger", "--key", kid])
+ self.assertIn(jid, out.getvalue()); self.assertIn("-$ 0.0200", out.getvalue())
+
+ def test_pod_lost_not_billed_and_cancel(self):
+ full, kid = self.s.admin_key(20)
+ jid = self.s.js("POST", "/v1/jobs", body={"input": "in.lmp"}, key=full)[1]["id"]
+ up = self.s.app.blob_url(f"{jid}.in.tar.gz")[0]; self.s.req("PUT", up, raw=b"x"); self.s.js("POST", f"/v1/jobs/{jid}/start", key=full)
+ tok = self.launch_token()
+ self.s.js("POST", f"/internal/jobs/{jid}/heartbeat", body={"thermo_tail": [], "elapsed_s": 0}, key=tok)
+ self.s.app.db.set_job(jid, last_hb=time.time() - 1000, started="2020-01-01T00:00:00Z")
+ self.s.app.watchdog_once()
+ st = self.s.js("GET", f"/v1/jobs/{jid}", key=full)[1]
+ self.assertEqual((st["state"], st["error"], st["cost_usd"]), ("failed", "pod_lost", 0.0))
+ self.assertEqual(self.s.js("GET", "/v1/me", key=full)[1]["balance_usd"], 20.0)
+ # Cancel a fresh job.
+ j2 = self.s.js("POST", "/v1/jobs", body={"input": "in.lmp"}, key=full)[1]["id"]
+ code, st = self.s.js("DELETE", f"/v1/jobs/{j2}", key=full); self.assertEqual(code, 202); self.assertEqual(st["state"], "cancelled")
+
+class TestLauncherFlow(Base):
+ """Job lifecycle with a FakeLauncher injected: pods are created at start and gone at every terminal state."""
+ runners_open = True; fake_launcher = True
+
+ def started_job(self, credit=20):
+ full, kid = self.s.admin_key(credit)
+ jid = self.s.js("POST", "/v1/jobs", body={"input": "in.lmp", "gpu": "rtx4090", "wall_limit_s": 3600}, key=full)[1]["id"]
+ self.s.req("PUT", self.s.app.blob_url(f"{jid}.in.tar.gz")[0], raw=b"deck")
+ code, j = self.s.js("POST", f"/v1/jobs/{jid}/start", key=full); self.assertEqual((code, j["state"]), (202, "queued"))
+ self.assertTrue(self.s.app.join_bg()); return full, jid
+
+ def pod_token(self, jid):
+ pod = self.s.app.db.job(jid)["pod_id"]; return self.fake.pods[pod]["env"]["MDE_JOB_TOKEN"]
+
+ def test_start_launches_pod_then_done_deletes_it(self):
+ self.assertEqual(self.s.js("GET", "/v1/health")[1]["launcher"], "fake")
+ full, jid = self.started_job()
+ self.assertFalse(os.path.exists(self.s.app.launch_env)) # no dev launch.env with a launcher
+ row = self.s.app.db.job(jid); st = self.s.js("GET", f"/v1/jobs/{jid}", key=full)[1]
+ self.assertEqual(st["state"], "launching"); self.assertEqual(st["pod_id"], row["pod_id"]); self.assertTrue(row["launched_at"])
+ pod = self.fake.pods[row["pod_id"]]
+ self.assertEqual(pod["name"], "mde-" + jid); self.assertEqual(pod["env"]["MDE_JOB_ID"], jid); self.assertEqual(pod["env"]["MDE_ENDPOINT"], self.s.base)
+ tok = pod["env"]["MDE_JOB_TOKEN"]; self.assertTrue(tok.startswith("jt_")); self.assertEqual(row["token_hash"], E.sha256(tok))
+ self.assertNotIn(tok, self._log.getvalue())
+ # Pod boots, heartbeats -> running; results; done -> pod deleted in the background.
+ code, spec = self.s.js("GET", f"/internal/jobs/{jid}", key=tok); self.assertEqual(code, 200); self.assertEqual(spec["wall_limit_s"], 3600)
+ self.s.js("POST", f"/internal/jobs/{jid}/heartbeat", body={"thermo_tail": ["x"], "elapsed_s": 1}, key=tok)
+ self.assertEqual(self.s.js("GET", f"/v1/jobs/{jid}", key=full)[1]["state"], "running")
+ self.s.req("PUT", spec["results_put_url"], raw=b"out")
+ code, j = self.s.js("POST", f"/internal/jobs/{jid}/done", body={"exitcode": 0, "elapsed_s": 10, "results_bytes": 3}, key=tok)
+ self.assertEqual((code, j["state"]), (200, "done")); self.assertTrue(self.s.app.join_bg())
+ self.assertEqual(self.fake.deleted, [row["pod_id"]]); self.assertEqual(self.fake.pods, {})
+ self.assertIn('"ev":"pod.deleted"', self._log.getvalue())
+ self.assertEqual(self.s.js("GET", f"/v1/jobs/{jid}", key=full)[1]["billed_s"], 10)
+
+ def test_cancel_deletes_pod(self):
+ full, jid = self.started_job(); pod = self.s.app.db.job(jid)["pod_id"]
+ code, st = self.s.js("DELETE", f"/v1/jobs/{jid}", key=full); self.assertEqual((code, st["state"]), (202, "cancelled"))
+ self.assertTrue(self.s.app.join_bg()); self.assertEqual(self.fake.deleted, [pod]); self.assertEqual(self.fake.pods, {})
+
+ def test_pod_lost_deletes_pod_unbilled(self):
+ full, jid = self.started_job(); pod = self.s.app.db.job(jid)["pod_id"]; tok = self.pod_token(jid)
+ self.s.js("POST", f"/internal/jobs/{jid}/heartbeat", body={"thermo_tail": [], "elapsed_s": 0}, key=tok)
+ self.s.app.db.set_job(jid, last_hb=time.time() - 1000, started="2020-01-01T00:00:00Z")
+ self.s.app.watchdog_once(); self.assertTrue(self.s.app.join_bg())
+ st = self.s.js("GET", f"/v1/jobs/{jid}", key=full)[1]
+ self.assertEqual((st["state"], st["error"], st["cost_usd"]), ("failed", "pod_lost", 0.0))
+ self.assertEqual(self.fake.deleted, [pod]); self.assertEqual(self.s.js("GET", "/v1/me", key=full)[1]["balance_usd"], 20.0)
+
+ def test_launch_timeout_is_no_capacity_and_deletes_pod(self):
+ full, jid = self.started_job(); pod = self.s.app.db.job(jid)["pod_id"]
+ self.s.app.watchdog_once(); self.assertEqual(self.s.app.db.job(jid)["state"], "launching") # fresh: untouched
+ self.s.app.db.set_job(jid, launched_at="2020-01-01T00:00:00Z")
+ self.s.app.watchdog_once(); self.assertTrue(self.s.app.join_bg())
+ st = self.s.js("GET", f"/v1/jobs/{jid}", key=full)[1]
+ self.assertEqual((st["state"], st["error"], st["cost_usd"]), ("failed", "no_capacity", 0.0))
+ self.assertEqual(self.fake.deleted, [pod]); self.assertIsNone(self.s.app.db.job(jid)["token_hash"])
+ self.assertIn('"ev":"job.launch_timeout"', self._log.getvalue())
+ self.assertEqual(self.s.js("GET", "/v1/me", key=full)[1]["balance_usd"], 20.0)
+
+ def test_no_capacity_from_launcher(self):
+ self.fake.fail_create = True
+ full, jid = self.started_job()
+ st = self.s.js("GET", f"/v1/jobs/{jid}", key=full)[1]
+ self.assertEqual((st["state"], st["error"], st["cost_usd"], st["pod_id"]), ("failed", "no_capacity", 0.0, None))
+ self.assertEqual(self.fake.pods, {}); self.assertIsNone(self.s.app.db.job(jid)["token_hash"])
+ self.assertIn('"ev":"job.no_capacity"', self._log.getvalue())
+ self.assertEqual(self.s.js("GET", "/v1/me", key=full)[1]["balance_usd"], 20.0)
+
+ def test_reaper(self):
+ full, jid = self.started_job(); live = self.s.app.db.job(jid)["pod_id"]
+ orphan = self.fake.add_pod("mde-MDJOB-20260101-NOJOB1")
+ other = self.fake.add_pod("someone-elses-pod") # not ours: never touched
+ # A terminal job whose pod delete failed earlier (simulate by re-adding the pod after cancel).
+ full2, jid2 = self.started_job(); self.s.js("DELETE", f"/v1/jobs/{jid2}", key=full2); self.s.app.join_bg()
+ stale = self.fake.add_pod("mde-" + jid2, pod_id=self.s.app.db.job(jid2)["pod_id"]); self.fake.deleted.clear()
+ # A running job whose pod is older than wall_limit_s + 20 min.
+ full3, jid3 = self.started_job(); old = self.s.app.db.job(jid3)["pod_id"]
+ self.fake.pods[old]["createdAt"] = "2020-01-01T00:00:00.000Z"
+ n = self.s.app.reaper_once()
+ self.assertEqual(n, 3); self.assertEqual(sorted(self.fake.deleted), sorted([orphan, stale, old]))
+ self.assertIn(live, self.fake.pods); self.assertIn(other, self.fake.pods)
+ logs = self._log.getvalue()
+ for reason in ("no_job", "job_terminal", "overage"): self.assertIn('"reason":"%s"' % reason, logs)
+ # Stuck pod: three failed passes -> reaper.stuck.
+ stuck = self.fake.add_pod("mde-MDJOB-20260101-STUCK1"); self.fake.fail_delete.add(stuck)
+ for _ in range(3): self.s.app.reaper_once()
+ self.assertEqual(self.s.app.reaper_fail[stuck], 3); self.assertIn('"ev":"reaper.stuck"', self._log.getvalue())
+ self.fake.fail_delete.clear(); self.s.app.reaper_once(); self.assertNotIn(stuck, self.s.app.reaper_fail)
+
+class TestRunPodLauncher(unittest.TestCase):
+ """Request shaping against a monkeypatched transport; no network."""
+ KEY = "rpa_TESTKEY_" + "z" * 24
+ def launcher(self, **env):
+ base = {"RUNPOD_API_KEY": self.KEY, "MDE_PUBLIC_URL": "https://api.example.test/", "MDE_DB": "/nonexistent/x.sqlite"}
+ base.update(env); return L.RunPodLauncher(E.Config(base))
+
+ def test_create_body_and_ladder(self):
+ lc = self.launcher(); calls = []
+ def fake_request(method, path, body=None):
+ calls.append((method, path, body)); return (500, '{"error":"no capacity"}') if len(calls) == 1 else (201, '{"id":"pod123","name":"x"}')
+ lc._request = fake_request
+ log = io.StringIO()
+ with redirect_stdout(log): pid = lc.create("MDJOB-20260906-ABC123", "jt_" + "0" * 32, 3600, "rtx4090")
+ self.assertEqual(pid, "pod123"); self.assertEqual(len(calls), 2)
+ for (m, p, b), (cloud, gid) in zip(calls, L.DEFAULT_LADDER):
+ self.assertEqual((m, p), ("POST", "/v2/pods")); self.assertEqual(b["cloud"], cloud)
+ self.assertEqual(b["gpu"], {"id": gid, "count": 1, "minCudaVersion": "12.4"}); self.assertEqual(b["disk"], 20)
+ self.assertEqual(b["name"], "mde-MDJOB-20260906-ABC123"); self.assertEqual(b["image"], L.DEFAULT_IMAGE)
+ self.assertEqual(b["env"], {"MDE_ENDPOINT": "https://api.example.test", "MDE_JOB_ID": "MDJOB-20260906-ABC123", "MDE_JOB_TOKEN": "jt_" + "0" * 32, "MDE_WALL_LIMIT_S": "3600"})
+ self.assertNotIn("dataCenterIds", b)
+ out = log.getvalue(); self.assertEqual(out.count('"ev":"launch.attempt"'), 2); self.assertIn('"code":500', out)
+ self.assertNotIn(self.KEY, out); self.assertNotIn("jt_" + "0" * 32, out)
+
+ def test_config_overrides(self):
+ lc = self.launcher(MDE_RUNNER_IMAGE="ghcr.io/x/y:z", MDE_POD_DISK_GB="40", MDE_MIN_CUDA="12.8",
+ MDE_GPU_LADDER="SECURE:NVIDIA A100 80GB PCIe, community:NVIDIA GeForce RTX 4090")
+ self.assertEqual(lc.ladder, [("SECURE", "NVIDIA A100 80GB PCIe"), ("COMMUNITY", "NVIDIA GeForce RTX 4090")])
+ b = lc.pod_body("J", "t", "SECURE", "NVIDIA A100 80GB PCIe")
+ self.assertEqual((b["image"], b["disk"], b["gpu"]["minCudaVersion"]), ("ghcr.io/x/y:z", 40, "12.8"))
+ with self.assertRaises(ValueError): L.parse_ladder("PRIVATE:foo")
+ with self.assertRaises(ValueError): L.parse_ladder("nocolon")
+
+ def test_all_rungs_fail_is_no_capacity(self):
+ lc = self.launcher(); lc._request = lambda m, p, body=None: (422, '{"error":"bad"}')
+ with redirect_stdout(io.StringIO()), self.assertRaises(L.NoCapacity): lc.create("J", "t", 60, "any")
+ # 201 without an id is also a failed rung, and its body is not echoed.
+ lc._request = lambda m, p, body=None: (201, '{"env":{"MDE_JOB_TOKEN":"jt_secret"}}'); out = io.StringIO()
+ with redirect_stdout(out), self.assertRaises(L.NoCapacity): lc.create("J", "t", 60, "any")
+ self.assertNotIn("jt_secret", out.getvalue())
+
+ def test_delete_semantics(self):
+ lc = self.launcher(); lc.backoff_s = 0; seq = []
+ def scripted(codes):
+ it = iter(codes)
+ def f(m, p, body=None):
+ seq.append((m, p)); return next(it), "{}"
+ return f
+ lc._request = scripted([204]); self.assertTrue(lc.delete("p1"))
+ lc._request = scripted([404]); self.assertTrue(lc.delete("p1"))
+ lc._request = scripted([429, 500, 204]); self.assertTrue(lc.delete("p1"))
+ lc._request = scripted([500, 500, 500])
+ with self.assertRaises(L.LauncherError): lc.delete("p1")
+ seq.clear(); lc._request = scripted([403])
+ with self.assertRaises(L.LauncherError): lc.delete("p1")
+ self.assertEqual(seq, [("DELETE", "/v2/pods/p1")]) # other 4xx: no retry
+
+ def test_get_and_list(self):
+ lc = self.launcher()
+ lc._request = lambda m, p, body=None: (200, '{"items":[{"id":"a","name":"mde-x"}]}')
+ self.assertEqual([p["id"] for p in lc.list_pods()], ["a"])
+ lc._request = lambda m, p, body=None: (200, '[{"id":"b"}]'); self.assertEqual(lc.list_pods()[0]["id"], "b")
+ lc._request = lambda m, p, body=None: (200, '{"pods":[{"id":"c","name":"mde-y"}]}') # LIVE shape (2026-09-06)
+ self.assertEqual([p["id"] for p in lc.list_pods()], ["c"])
+ lc._request = lambda m, p, body=None: (404, ""); self.assertIsNone(lc.get("zz"))
+ lc._request = lambda m, p, body=None: (200, '{"id":"zz","status":"RUNNING"}'); self.assertEqual(lc.get("zz")["status"], "RUNNING")
+ lc._request = lambda m, p, body=None: (500, "boom")
+ with self.assertRaises(L.LauncherError): lc.list_pods()
+
+ def test_authorization_header_and_url(self):
+ lc = self.launcher(); seen = []
+ def fake_open(req):
+ seen.append(req); return 204, ""
+ lc._open = fake_open; lc.delete("p9")
+ req = seen[0]; self.assertEqual(req.full_url, "https://api.runpod.io/v2/pods/p9"); self.assertEqual(req.get_method(), "DELETE")
+ auth = req.get_header("Authorization"); self.assertTrue(auth and auth.startswith("Bearer ") and len(auth) == len("Bearer ") + len(self.KEY))
+ self.assertEqual(req.timeout if hasattr(req, "timeout") else lc.timeout, lc.timeout)
+ with self.assertRaises(ValueError): L.RunPodLauncher(E.Config({"RUNPOD_API_KEY": "", "MDE_DB": "/nonexistent/x"}))
+
+ def test_pod_age(self):
+ self.assertAlmostEqual(L.pod_age_s({"createdAt": "2020-01-01T00:00:00.000Z"}, now_ts=1577836800 + 90), 90, places=3)
+ self.assertAlmostEqual(L.pod_age_s({"createdAt": "2020-01-01T01:00:00+01:00"}, now_ts=1577836800 + 5), 5, places=3)
+ self.assertIsNone(L.pod_age_s({"createdAt": "garbage"})); self.assertIsNone(L.pod_age_s({}))
+
+class TestUnits(unittest.TestCase):
+ def test_schema_migration_adds_pod_columns(self):
+ d = tempfile.mkdtemp(prefix="mde-mig-"); path = os.path.join(d, "old.sqlite")
+ import sqlite3
+ c = sqlite3.connect(path)
+ c.executescript(E.SCHEMA.replace(", pod_id text, launched_at text", "")); c.close()
+ db = E.DB(path); cols = {r["name"] for r in db.q("pragma table_info(jobs)")}
+ self.assertIn("pod_id", cols); self.assertIn("launched_at", cols)
+ db2 = E.DB(path); db.c.close(); db2.c.close(); shutil.rmtree(d, ignore_errors=True) # second open: duplicate column ignored
+
+ def test_signature_roundtrip(self):
+ p = b'{"a":1}'; h = E.stripe_sign(p, "s")
+ self.assertTrue(E.verify_stripe_signature(p, h, "s")); self.assertFalse(E.verify_stripe_signature(p, h, "t"))
+ self.assertFalse(E.verify_stripe_signature(p, "t=abc,v1=00", "s")); self.assertFalse(E.verify_stripe_signature(p, h, ""))
+ def test_parse_packs_rates(self):
+ self.assertEqual(E.parse_packs("price_a:25:12.5, price_b:100:50"), {"price_a": (25.0, 12.5), "price_b": (100.0, 50.0)})
+ self.assertEqual(E.parse_rates(""), {"any": 2.0, "rtx4090": 2.0}); self.assertEqual(E.parse_rates("any:2,a100:4"), {"any": 2.0, "a100": 4.0})
+ def test_env_file(self):
+ with tempfile.NamedTemporaryFile("w", suffix=".env", delete=False) as f: f.write("# c\nX_MDE_T=\"v 1\"\nY_MDE_T=2\n")
+ os.environ.pop("X_MDE_T", None); os.environ["Y_MDE_T"] = "keep"; E.load_env_file(f.name); os.unlink(f.name)
+ self.assertEqual(os.environ["X_MDE_T"], "v 1"); self.assertEqual(os.environ["Y_MDE_T"], "keep")
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/hosted/mock/mock_endpoint.py b/hosted/mock/mock_endpoint.py
new file mode 100644
index 0000000..73488aa
--- /dev/null
+++ b/hosted/mock/mock_endpoint.py
@@ -0,0 +1,159 @@
+#!/usr/bin/env python3
+"""Local mock of the MDEngine hosted endpoint (hosted/CONTRACT.md v1). Stdlib only.
+
+Purpose: let the pull-runner (docker/runner-gpu/runner.sh) and the clients (CLI/MCP/app) be
+developed and tested offline. Same routes and JSON as the real endpoint; storage = sqlite + a local
+blob dir served at /blob/ (GET/PUT) standing in for presigned object-storage URLs. NOT the
+production server: no TLS, no key hashing, no pod launcher (a "launch" here just marks the job
+queued and prints the env a pod would get).
+
+ python3 mock_endpoint.py --port 8787 --data /tmp/mde-mock
+ curl -H 'Authorization: Bearer mde_test' localhost:8787/v1/me
+"""
+import argparse, json, os, secrets, sqlite3, time, uuid
+from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
+from urllib.parse import urlparse
+
+RATES = {"any": 2.0, "rtx4090": 2.0, "a100": 4.0} # $/GPU-h placeholder until GJOB-092
+STATES = "created uploaded queued launching running uploading done failed cancelled".split()
+
+def now(): return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
+def job_id(): return "MDJOB-%s-%s" % (time.strftime("%Y%m%d", time.gmtime()), secrets.token_hex(3).upper())
+
+class DB:
+ def __init__(self, path):
+ self.c = sqlite3.connect(path, check_same_thread=False); self.c.row_factory = sqlite3.Row
+ self.c.executescript("""
+ create table if not exists keys(key text primary key, balance_usd real, created text);
+ create table if not exists jobs(id text primary key, key text, token text, spec text, state text,
+ created text, started text, finished text, gpu text, rate real, billed_s int default 0,
+ thermo text default '[]', exitcode int, error text, attempt int default 1, last_hb real);
+ """)
+ if not self.c.execute("select 1 from keys where key='mde_test'").fetchone():
+ self.c.execute("insert into keys values('mde_test', 20.0, ?)", (now(),)); self.c.commit()
+ def key(self, k): return self.c.execute("select * from keys where key=?", (k,)).fetchone()
+ def job(self, jid): return self.c.execute("select * from jobs where id=?", (jid,)).fetchone()
+ def set(self, jid, **kw):
+ cols = ", ".join(f"{k}=?" for k in kw); self.c.execute(f"update jobs set {cols} where id=?", (*kw.values(), jid)); self.c.commit()
+
+def status(j):
+ spec = json.loads(j["spec"])
+ billed = j["billed_s"]
+ if j["state"] == "running" and j["started"]:
+ billed = int(time.time() - time.mktime(time.strptime(j["started"], "%Y-%m-%dT%H:%M:%SZ")) + time.timezone)
+ return {"id": j["id"], "state": j["state"], "states": "|".join(STATES), "created": j["created"],
+ "started": j["started"], "finished": j["finished"], "gpu": j["gpu"], "rate_usd_per_h": j["rate"],
+ "billed_s": billed, "cost_usd": round(billed * j["rate"] / 3600, 4), "thermo_tail": json.loads(j["thermo"]),
+ "exitcode": j["exitcode"], "error": j["error"], "attempt": j["attempt"], "label": spec.get("label")}
+
+class H(BaseHTTPRequestHandler):
+ db: DB; data: str; base: str
+ def log_message(self, *a): pass
+ def send(self, code, obj=None, raw=None, ctype="application/json"):
+ body = raw if raw is not None else (json.dumps(obj).encode() if obj is not None else b"")
+ self.send_response(code); self.send_header("Content-Type", ctype); self.send_header("Content-Length", str(len(body)))
+ self.end_headers(); self.wfile.write(body)
+ def body(self):
+ n = int(self.headers.get("Content-Length") or 0); return self.rfile.read(n) if n else b""
+ def bearer(self):
+ a = self.headers.get("Authorization", ""); return a[7:] if a.startswith("Bearer ") else None
+ def blob(self, name): return os.path.join(self.data, "blobs", name)
+
+ def do_GET(self):
+ p = urlparse(self.path).path.rstrip("/"); parts = p.split("/")
+ if parts[1] == "blob":
+ f = self.blob(parts[2])
+ if not os.path.exists(f): return self.send(404, {"error": "no blob"})
+ with open(f, "rb") as fh: return self.send(200, raw=fh.read(), ctype="application/gzip")
+ if parts[1] == "internal": # pod side
+ j = self.db.job(parts[3]) if len(parts) > 3 else None
+ if not j or self.bearer() != j["token"]: return self.send(401, {"error": "bad token"})
+ spec = json.loads(j["spec"])
+ return self.send(200, {"input_url": f"{self.base}/blob/{j['id']}.in.tar.gz", "input": spec["input"],
+ "launch": spec.get("launch", "default"), "wall_limit_s": spec.get("wall_limit_s", 86400),
+ "results_put_url": f"{self.base}/blob/{j['id']}.out.tar.gz"})
+ k = self.db.key(self.bearer() or "")
+ if not k: return self.send(401, {"error": "bad api key"})
+ if p == "/v1/me": return self.send(200, {"balance_usd": k["balance_usd"], "rate_table": RATES, "keys_created": k["created"]})
+ if p == "/v1/jobs":
+ rows = self.db.c.execute("select * from jobs where key=? order by created desc limit 50", (k["key"],)).fetchall()
+ return self.send(200, {"jobs": [status(j) for j in rows]})
+ if len(parts) >= 4 and parts[2] == "jobs":
+ j = self.db.job(parts[3])
+ if not j or j["key"] != k["key"]: return self.send(404, {"error": "no job"})
+ if len(parts) == 4: return self.send(200, status(j))
+ if parts[4] == "results":
+ if j["state"] not in ("done", "failed"): return self.send(409, {"error": "not finished"})
+ f = self.blob(f"{j['id']}.out.tar.gz"); sz = os.path.getsize(f) if os.path.exists(f) else 0
+ return self.send(200, {"download_url": f"{self.base}/blob/{j['id']}.out.tar.gz", "expires": None, "bytes": sz})
+ self.send(404, {"error": "no route"})
+
+ def do_PUT(self):
+ parts = urlparse(self.path).path.split("/")
+ if parts[1] != "blob": return self.send(404, {"error": "no route"})
+ os.makedirs(os.path.dirname(self.blob("x")), exist_ok=True)
+ with open(self.blob(parts[2]), "wb") as fh: fh.write(self.body())
+ jid = parts[2].split(".")[0]
+ if parts[2].endswith(".in.tar.gz") and self.db.job(jid) and self.db.job(jid)["state"] == "created": self.db.set(jid, state="uploaded")
+ self.send(200, {"ok": True})
+
+ def do_POST(self):
+ p = urlparse(self.path).path.rstrip("/"); parts = p.split("/")
+ if parts[1] == "internal": # pod side: heartbeat / done
+ j = self.db.job(parts[3])
+ if not j or self.bearer() != j["token"]: return self.send(401, {"error": "bad token"})
+ b = json.loads(self.body() or b"{}")
+ if parts[4] == "heartbeat":
+ kw = {"thermo": json.dumps(b.get("thermo_tail", [])[-20:]), "last_hb": time.time()}
+ if j["state"] in ("queued", "launching"): kw.update(state="running", started=now())
+ self.db.set(j["id"], **kw); return self.send(200, {"ok": True})
+ if parts[4] == "done":
+ rc = int(b.get("exitcode", 1)); err = b.get("error")
+ started = j["started"] or now(); billed = int(b.get("elapsed_s", 0))
+ have = os.path.exists(self.blob(f"{j['id']}.out.tar.gz"))
+ state = "done" if rc == 0 and have else "failed"
+ if not have and not err: err = "no_results"
+ self.db.set(j["id"], state=state, finished=now(), started=started, exitcode=rc, error=err, billed_s=billed, token=secrets.token_hex(4)) # token invalidated
+ cost = billed * j["rate"] / 3600
+ if err not in ("pod_lost",): self.db.c.execute("update keys set balance_usd=balance_usd-? where key=?", (cost, j["key"])); self.db.c.commit()
+ print(f"[mock] {j['id']} -> {state} rc={rc} err={err} billed={billed}s cost=${cost:.4f}", flush=True)
+ return self.send(200, {"ok": True, "state": state})
+ k = self.db.key(self.bearer() or "")
+ if not k: return self.send(401, {"error": "bad api key"})
+ if p == "/v1/jobs":
+ spec = json.loads(self.body() or b"{}")
+ if "input" not in spec: return self.send(400, {"error": "input required"})
+ gpu = spec.get("gpu", "any"); rate = RATES.get(gpu)
+ if rate is None: return self.send(400, {"error": "unknown gpu"})
+ if k["balance_usd"] < rate * max(int(spec.get("estimate_s", 0)), 900) / 3600: return self.send(402, {"error": "insufficient balance"})
+ jid = job_id(); tok = "jt_" + secrets.token_hex(16)
+ self.db.c.execute("insert into jobs(id,key,token,spec,state,created,gpu,rate) values(?,?,?,?,?,?,?,?)",
+ (jid, k["key"], tok, json.dumps(spec), "created", now(), gpu, rate)); self.db.c.commit()
+ return self.send(201, {"id": jid, "upload_url": f"{self.base}/blob/{jid}.in.tar.gz", "upload_expires": None})
+ if len(parts) == 5 and parts[2] == "jobs" and parts[4] == "start":
+ j = self.db.job(parts[3])
+ if not j or j["key"] != k["key"]: return self.send(404, {"error": "no job"})
+ if j["state"] != "uploaded": return self.send(409, {"error": f"state is {j['state']}"})
+ self.db.set(j["id"], state="queued")
+ # The real endpoint launches a pod here. The mock prints what the launcher would inject.
+ print(f"[mock] LAUNCH {j['id']}: MDE_ENDPOINT={self.base} MDE_JOB_ID={j['id']} MDE_JOB_TOKEN={j['token']}", flush=True)
+ with open(os.path.join(self.data, "launch.env"), "w") as fh:
+ fh.write(f"MDE_ENDPOINT={self.base}\nMDE_JOB_ID={j['id']}\nMDE_JOB_TOKEN={j['token']}\n")
+ return self.send(202, {"id": j["id"], "state": "queued"})
+ self.send(404, {"error": "no route"})
+
+ def do_DELETE(self):
+ parts = urlparse(self.path).path.rstrip("/").split("/")
+ k = self.db.key(self.bearer() or "")
+ if not k or len(parts) != 4: return self.send(401, {"error": "bad api key"})
+ j = self.db.job(parts[3])
+ if not j or j["key"] != k["key"]: return self.send(404, {"error": "no job"})
+ if j["state"] in ("done", "failed", "cancelled"): return self.send(409, {"error": "terminal"})
+ self.db.set(j["id"], state="cancelled", finished=now(), error="cancelled"); self.send(202, {"id": j["id"], "state": "cancelled"})
+
+if __name__ == "__main__":
+ ap = argparse.ArgumentParser(); ap.add_argument("--port", type=int, default=8787); ap.add_argument("--data", default="/tmp/mde-mock")
+ a = ap.parse_args(); os.makedirs(os.path.join(a.data, "blobs"), exist_ok=True)
+ H.db = DB(os.path.join(a.data, "mock.sqlite")); H.data = a.data; H.base = f"http://127.0.0.1:{a.port}"
+ print(f"[mock] listening on {H.base} data={a.data} test key: mde_test ($20)", flush=True)
+ ThreadingHTTPServer(("127.0.0.1", a.port), H).serve_forever()