Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -256,6 +256,92 @@ def completions(request: Request, body: ChatRequest):
|
|
| 256 |
"iom_receipt_id": receipt["receipt_id"]
|
| 257 |
}
|
| 258 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
# ========= UI helpers =========
|
| 260 |
def _load_events_df():
|
| 261 |
if not os.path.exists(EVENTS_PATH):
|
|
@@ -351,7 +437,7 @@ with api_ui:
|
|
| 351 |
gr.Markdown("# IoM-TR Receipts β Portable Governance (NIM + HF Space)")
|
| 352 |
|
| 353 |
with gr.Tab("Ask & Verify (in this Space)"):
|
| 354 |
-
gr.Markdown("Type a prompt, click **Ask & Get Receipt**.
|
| 355 |
p2 = gr.Textbox(label="Prompt", value="In one sentence, what are portable AI governance receipts?")
|
| 356 |
ask = gr.Button("Ask & Get Receipt")
|
| 357 |
ans = gr.Textbox(label="Answer")
|
|
@@ -377,7 +463,45 @@ with api_ui:
|
|
| 377 |
verdict2 = gr.Textbox(label="Result")
|
| 378 |
check.click(verify_local, inputs=[p, a, rj, pepper], outputs=verdict2)
|
| 379 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 380 |
# Expose the ASGI app for Spaces (Docker or Gradio SDK)
|
| 381 |
-
# Optional: exposing demo can help the Gradio runner, but not required for Docker
|
| 382 |
demo = api_ui
|
| 383 |
-
app
|
|
|
|
| 256 |
"iom_receipt_id": receipt["receipt_id"]
|
| 257 |
}
|
| 258 |
|
| 259 |
+
# ========= Session & Merkle helpers =========
|
| 260 |
+
SESSIONS_DIR = pathlib.Path("/tmp/sessions"); SESSIONS_DIR.mkdir(exist_ok=True)
|
| 261 |
+
|
| 262 |
+
def _session_path(sid: str) -> pathlib.Path:
|
| 263 |
+
return SESSIONS_DIR / f"{sid}.json"
|
| 264 |
+
|
| 265 |
+
def merkle_root_from_leaves(leaves: list[str]) -> str:
|
| 266 |
+
"""Compute a simple Merkle root from hex-string leaves.
|
| 267 |
+
Parent = sha256_hex(left + right) (deterministic demo)."""
|
| 268 |
+
if not leaves:
|
| 269 |
+
return ""
|
| 270 |
+
level = leaves[:]
|
| 271 |
+
while len(level) > 1:
|
| 272 |
+
if len(level) % 2 == 1:
|
| 273 |
+
level.append(level[-1]) # duplicate last if odd
|
| 274 |
+
nxt = []
|
| 275 |
+
for i in range(0, len(level), 2):
|
| 276 |
+
nxt.append(sha256_hex(level[i] + level[i+1]))
|
| 277 |
+
level = nxt
|
| 278 |
+
return level[0]
|
| 279 |
+
|
| 280 |
+
def leaf_from_receipt(r: dict) -> str:
|
| 281 |
+
"""Leaf = sha256( prompt_sha256 + response_sha256 )."""
|
| 282 |
+
p = r.get("prompt_sha256", "")
|
| 283 |
+
a = r.get("response_sha256", "")
|
| 284 |
+
return sha256_hex(p + a)
|
| 285 |
+
|
| 286 |
+
def session_new() -> dict:
|
| 287 |
+
sid = str(uuid.uuid4())
|
| 288 |
+
doc = {"id": sid, "created": datetime.utcnow().isoformat()+"Z",
|
| 289 |
+
"receipts": [], "leaf_hashes": [], "policy_ids": [], "merkle_root": ""}
|
| 290 |
+
_session_path(sid).write_text(json.dumps(doc, indent=2))
|
| 291 |
+
return doc
|
| 292 |
+
|
| 293 |
+
def session_load(sid: str) -> dict:
|
| 294 |
+
p = _session_path(sid)
|
| 295 |
+
if not p.exists():
|
| 296 |
+
raise FileNotFoundError("session not found")
|
| 297 |
+
return json.loads(p.read_text())
|
| 298 |
+
|
| 299 |
+
def session_save(doc: dict):
|
| 300 |
+
_session_path(doc["id"]).write_text(json.dumps(doc, indent=2))
|
| 301 |
+
|
| 302 |
+
def session_add_receipt(sid: str, rid: str) -> dict:
|
| 303 |
+
doc = session_load(sid)
|
| 304 |
+
rp = RECEIPTS_DIR / f"{rid}.json"
|
| 305 |
+
if not rp.exists():
|
| 306 |
+
raise FileNotFoundError("receipt not found")
|
| 307 |
+
rj = json.loads(rp.read_text())
|
| 308 |
+
leaf = leaf_from_receipt(rj)
|
| 309 |
+
doc["receipts"].append(rid)
|
| 310 |
+
doc["leaf_hashes"].append(leaf)
|
| 311 |
+
pid = rj.get("policy_id")
|
| 312 |
+
if pid and pid not in doc["policy_ids"]:
|
| 313 |
+
doc["policy_ids"].append(pid)
|
| 314 |
+
doc["merkle_root"] = "" # will be set on finish
|
| 315 |
+
session_save(doc)
|
| 316 |
+
return doc
|
| 317 |
+
|
| 318 |
+
def session_finish(sid: str) -> dict:
|
| 319 |
+
doc = session_load(sid)
|
| 320 |
+
doc["merkle_root"] = merkle_root_from_leaves(doc["leaf_hashes"])
|
| 321 |
+
doc["finished"] = datetime.utcnow().isoformat()+"Z"
|
| 322 |
+
session_save(doc)
|
| 323 |
+
return doc
|
| 324 |
+
|
| 325 |
+
# ========= Session API =========
|
| 326 |
+
@api.post("/session/new")
|
| 327 |
+
def api_session_new():
|
| 328 |
+
doc = session_new()
|
| 329 |
+
return {"session_id": doc["id"]}
|
| 330 |
+
|
| 331 |
+
@api.post("/session/{sid}/add/{rid}")
|
| 332 |
+
def api_session_add(sid: str, rid: str):
|
| 333 |
+
doc = session_add_receipt(sid, rid)
|
| 334 |
+
return {"session_id": doc["id"], "count": len(doc["receipts"])}
|
| 335 |
+
|
| 336 |
+
@api.post("/session/{sid}/finish")
|
| 337 |
+
def api_session_finish(sid: str):
|
| 338 |
+
doc = session_finish(sid)
|
| 339 |
+
return {"session_id": doc["id"], "merkle_root": doc["merkle_root"]}
|
| 340 |
+
|
| 341 |
+
@api.get("/session/{sid}")
|
| 342 |
+
def api_session_get(sid: str):
|
| 343 |
+
return session_load(sid)
|
| 344 |
+
|
| 345 |
# ========= UI helpers =========
|
| 346 |
def _load_events_df():
|
| 347 |
if not os.path.exists(EVENTS_PATH):
|
|
|
|
| 437 |
gr.Markdown("# IoM-TR Receipts β Portable Governance (NIM + HF Space)")
|
| 438 |
|
| 439 |
with gr.Tab("Ask & Verify (in this Space)"):
|
| 440 |
+
gr.Markdown("Type a prompt, click **Ask & Get Receipt**. The UI shows the answer, a Receipt ID, the full receipt JSON, and local verification (SHA/HMAC).")
|
| 441 |
p2 = gr.Textbox(label="Prompt", value="In one sentence, what are portable AI governance receipts?")
|
| 442 |
ask = gr.Button("Ask & Get Receipt")
|
| 443 |
ans = gr.Textbox(label="Answer")
|
|
|
|
| 463 |
verdict2 = gr.Textbox(label="Result")
|
| 464 |
check.click(verify_local, inputs=[p, a, rj, pepper], outputs=verdict2)
|
| 465 |
|
| 466 |
+
with gr.Tab("Session Root"):
|
| 467 |
+
gr.Markdown("**Create a session β add last receipt(s) β finish β get one Merkle root + proof JSON.**")
|
| 468 |
+
|
| 469 |
+
sid_tb = gr.Textbox(label="Session ID", interactive=False)
|
| 470 |
+
sess_status = gr.Textbox(label="Status", interactive=False)
|
| 471 |
+
count_tb = gr.Number(label="Receipts in session", value=0, interactive=False)
|
| 472 |
+
root_tb = gr.Textbox(label="Merkle root", interactive=False)
|
| 473 |
+
proof_code = gr.Code(label="Session proof JSON (snapshot)", language="json")
|
| 474 |
+
proof_link = gr.Markdown()
|
| 475 |
+
|
| 476 |
+
def ui_session_new():
|
| 477 |
+
d = session_new()
|
| 478 |
+
return d["id"], "Session started", 0, "", "", f"[Open proof](/session/{d['id']})"
|
| 479 |
+
|
| 480 |
+
def ui_session_add_last(sid: str, last_rid: str):
|
| 481 |
+
if not sid:
|
| 482 |
+
return "", "Start a session first", 0, "", "", ""
|
| 483 |
+
if not last_rid:
|
| 484 |
+
return sid, "Run 'Ask & Get Receipt' first (to produce a receipt)", 0, "", "", f"[Open proof](/session/{sid})"
|
| 485 |
+
d = session_add_receipt(sid, last_rid)
|
| 486 |
+
snap = json.dumps(d, indent=2)
|
| 487 |
+
return d["id"], f"Added receipt {last_rid}", len(d["receipts"]), d.get("merkle_root",""), snap, f"[Open proof](/session/{d['id']})"
|
| 488 |
+
|
| 489 |
+
def ui_session_finish(sid: str):
|
| 490 |
+
if not sid:
|
| 491 |
+
return "", "Start a session first", 0, "", "", ""
|
| 492 |
+
d = session_finish(sid)
|
| 493 |
+
snap = json.dumps(d, indent=2)
|
| 494 |
+
return d["id"], "Session finished", len(d["receipts"]), d["merkle_root"], snap, f"[Open proof](/session/{d['id']})"
|
| 495 |
+
|
| 496 |
+
start_btn = gr.Button("Start new session")
|
| 497 |
+
add_btn = gr.Button("Add last receipt from 'Ask & Verify'")
|
| 498 |
+
finish_btn= gr.Button("Finish session (compute root)")
|
| 499 |
+
|
| 500 |
+
start_btn.click(ui_session_new, outputs=[sid_tb, sess_status, count_tb, root_tb, proof_code, proof_link])
|
| 501 |
+
# reuse the 'rid' Textbox from the Ask & Verify tab as input here
|
| 502 |
+
add_btn.click(ui_session_add_last, inputs=[sid_tb, rid], outputs=[sid_tb, sess_status, count_tb, root_tb, proof_code, proof_link])
|
| 503 |
+
finish_btn.click(ui_session_finish, inputs=[sid_tb], outputs=[sid_tb, sess_status, count_tb, root_tb, proof_code, proof_link])
|
| 504 |
+
|
| 505 |
# Expose the ASGI app for Spaces (Docker or Gradio SDK)
|
|
|
|
| 506 |
demo = api_ui
|
| 507 |
+
app = gr.mount_gradio_app(api, api_ui, path="/")
|