mmalam786 commited on
Commit
ca781ea
·
verified ·
1 Parent(s): de6d6dd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +133 -14
app.py CHANGED
@@ -25,6 +25,7 @@ RECEIPTS_DIR = pathlib.Path("/tmp/receipts"); RECEIPTS_DIR.mkdir(exist_ok=True)
25
  EVENTS_PATH = "/tmp/events.json"
26
  REQS_PATH = "/tmp/requests.jsonl"
27
  BUDGET_FILE = "/tmp/nim_calls_today.json"
 
28
 
29
  # ========= Helpers: canonicalization & hashing =========
30
  def canonicalize(text: str) -> str:
@@ -257,14 +258,12 @@ def completions(request: Request, body: ChatRequest):
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[:]
@@ -342,17 +341,83 @@ def api_session_finish(sid: str):
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):
348
- return pd.DataFrame({"info": ["No alerts yet"]})
349
- try:
350
- data = json.loads(open(EVENTS_PATH).read())
351
- for d in data:
352
- d.setdefault("prompt", ""); d.setdefault("snippet", "")
353
- return pd.DataFrame(data)[["ts", "type", "hits", "prompt", "snippet"]].iloc[::-1]
354
- except Exception:
355
- return pd.DataFrame({"info": ["Could not parse events"]})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
356
 
357
  def verify_local(prompt_text, response_text, receipt_json, pepper):
358
  try:
@@ -436,6 +501,7 @@ api_ui = gr.Blocks(title="IoM-TR Receipts — Portable Governance")
436
  with api_ui:
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?")
@@ -448,11 +514,13 @@ with api_ui:
448
  link = gr.Markdown()
449
  ask.click(ui_chat_with_receipt, inputs=p2, outputs=[ans, st2, rid, rjson, verdict, link])
450
 
 
451
  with gr.Tab("Dashboard"):
452
  refresh = gr.Button("Refresh")
453
  grid = gr.Dataframe(interactive=False)
454
  refresh.click(lambda: _load_events_df(), outputs=grid)
455
 
 
456
  with gr.Tab("Manual Verify (paste receipt)"):
457
  gr.Markdown("Paste the exact prompt & response you saw, and the receipt JSON from `/receipts/{id}`.")
458
  p = gr.Textbox(label="Prompt used")
@@ -463,6 +531,7 @@ with api_ui:
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
 
@@ -502,6 +571,56 @@ with api_ui:
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="/")
 
25
  EVENTS_PATH = "/tmp/events.json"
26
  REQS_PATH = "/tmp/requests.jsonl"
27
  BUDGET_FILE = "/tmp/nim_calls_today.json"
28
+ SESSIONS_DIR = pathlib.Path("/tmp/sessions"); SESSIONS_DIR.mkdir(exist_ok=True)
29
 
30
  # ========= Helpers: canonicalization & hashing =========
31
  def canonicalize(text: str) -> str:
 
258
  }
259
 
260
  # ========= Session & Merkle helpers =========
 
 
261
  def _session_path(sid: str) -> pathlib.Path:
262
  return SESSIONS_DIR / f"{sid}.json"
263
 
264
  def merkle_root_from_leaves(leaves: list[str]) -> str:
265
  """Compute a simple Merkle root from hex-string leaves.
266
+ Parent = sha256_hex(left + right) (deterministic demo). Duplicate last if odd per level."""
267
  if not leaves:
268
  return ""
269
  level = leaves[:]
 
341
  def api_session_get(sid: str):
342
  return session_load(sid)
343
 
344
+ # ========= Verify-All helpers (session + per-turn text) =========
345
+ def _list_sessions():
346
+ if not SESSIONS_DIR.exists():
347
+ return []
348
+ files = []
349
+ for p in SESSIONS_DIR.glob("*.json"):
350
+ try:
351
+ files.append((p, p.stat().st_mtime))
352
+ except Exception:
353
+ pass
354
+ files.sort(key=lambda x: x[1], reverse=True)
355
+ return [p.name[:-5] for p, _ in files]
356
+
357
+ def latest_session_id() -> str:
358
+ ids = _list_sessions()
359
+ return ids[0] if ids else ""
360
+
361
+ def recompute_session_root_and_table(sid: str):
362
+ """Return (computed_root, match_str, rows_for_table, proof_link_md)."""
363
+ doc = session_load(sid)
364
+ # Recompute each leaf from receipts
365
+ rows = []
366
+ leaves = []
367
+ for idx, rid in enumerate(doc.get("receipts", [])):
368
+ rp = RECEIPTS_DIR / f"{rid}.json"
369
+ if not rp.exists():
370
+ rows.append([rid, "(missing receipt)", "", False])
371
+ leaves.append("")
372
+ continue
373
+ rj = json.loads(rp.read_text())
374
+ recomputed_leaf = leaf_from_receipt(rj)
375
+ session_leaf = doc.get("leaf_hashes", [])[idx] if idx < len(doc.get("leaf_hashes", [])) else ""
376
+ ok = (session_leaf == recomputed_leaf) if session_leaf else True
377
+ rows.append([rid, session_leaf, recomputed_leaf, ok])
378
+ leaves.append(recomputed_leaf)
379
+ # Recompute Merkle root from recomputed leaves
380
+ comp_root = merkle_root_from_leaves(leaves)
381
+ ok_root = (comp_root == doc.get("merkle_root", ""))
382
+ return comp_root, f"Merkle root match: {ok_root}", rows, f"[Open proof](/session/{sid})"
383
+
384
+ def make_text_rows_for_session(sid: str):
385
+ """Return rows the user can paste prompts/answers into: [receipt_id, prompt, answer, sha_ok, hmac_ok]."""
386
+ doc = session_load(sid)
387
+ rows = []
388
+ for rid in doc.get("receipts", []):
389
+ rows.append([rid, "", "", False, (True if not AUDIT_PEPPER else False)])
390
+ return rows
391
+
392
+ def verify_text_rows(sid: str, rows: list, pepper: str):
393
+ """Rows: [receipt_id, prompt, answer, sha_ok, hmac_ok] -> fill sha_ok/hmac_ok."""
394
+ out = []
395
+ for rec in rows or []:
396
+ if len(rec) < 5:
397
+ rec = list(rec) + ["", "", False, False]
398
+ rid, prompt, answer, sha_ok, hmac_ok = rec[:5]
399
+ try:
400
+ rj = json.loads((RECEIPTS_DIR / f"{rid}.json").read_text())
401
+ except Exception:
402
+ out.append([rid, prompt, answer, False, False])
403
+ continue
404
+
405
+ # Canonicalize like the receipt did
406
+ def C(t):
407
+ t = unicodedata.normalize("NFKC", t or "").replace("\r\n", "\n").replace("\r", "\n")
408
+ t = "\n".join([ln.rstrip() for ln in t.split("\n")]); t = t.rstrip() + "\n"; return t
409
+ def s256(t): return hashlib.sha256(C(t).encode()).hexdigest()
410
+ def h256(k,t):
411
+ if not k: return ""
412
+ return hmac.new(k.encode(), C(t).encode(), hashlib.sha256).hexdigest()
413
+
414
+ sha_ok = (s256(prompt) == rj.get("prompt_sha256")) and (s256(answer) == rj.get("response_sha256"))
415
+ if pepper:
416
+ hmac_ok = (h256(pepper, prompt) == rj.get("prompt_hmac","")) and (h256(pepper, answer) == rj.get("response_hmac",""))
417
+ else:
418
+ hmac_ok = True # if no pepper is supplied, treat HMAC check as N/A/True
419
+ out.append([rid, prompt, answer, bool(sha_ok), bool(hmac_ok)])
420
+ return out
421
 
422
  def verify_local(prompt_text, response_text, receipt_json, pepper):
423
  try:
 
501
  with api_ui:
502
  gr.Markdown("# IoM-TR Receipts — Portable Governance (NIM + HF Space)")
503
 
504
+ # ---- Ask & Verify ----
505
  with gr.Tab("Ask & Verify (in this Space)"):
506
  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).")
507
  p2 = gr.Textbox(label="Prompt", value="In one sentence, what are portable AI governance receipts?")
 
514
  link = gr.Markdown()
515
  ask.click(ui_chat_with_receipt, inputs=p2, outputs=[ans, st2, rid, rjson, verdict, link])
516
 
517
+ # ---- Dashboard ----
518
  with gr.Tab("Dashboard"):
519
  refresh = gr.Button("Refresh")
520
  grid = gr.Dataframe(interactive=False)
521
  refresh.click(lambda: _load_events_df(), outputs=grid)
522
 
523
+ # ---- Manual Verify ----
524
  with gr.Tab("Manual Verify (paste receipt)"):
525
  gr.Markdown("Paste the exact prompt & response you saw, and the receipt JSON from `/receipts/{id}`.")
526
  p = gr.Textbox(label="Prompt used")
 
531
  verdict2 = gr.Textbox(label="Result")
532
  check.click(verify_local, inputs=[p, a, rj, pepper], outputs=verdict2)
533
 
534
+ # ---- Session Root ----
535
  with gr.Tab("Session Root"):
536
  gr.Markdown("**Create a session → add last receipt(s) → finish → get one Merkle root + proof JSON.**")
537
 
 
571
  add_btn.click(ui_session_add_last, inputs=[sid_tb, rid], outputs=[sid_tb, sess_status, count_tb, root_tb, proof_code, proof_link])
572
  finish_btn.click(ui_session_finish, inputs=[sid_tb], outputs=[sid_tb, sess_status, count_tb, root_tb, proof_code, proof_link])
573
 
574
+ # ---- Verify All (one place) ----
575
+ with gr.Tab("Verify All (one place)"):
576
+ gr.Markdown(
577
+ "Load a session (or leave blank to use the latest), recompute the Merkle root and leaf hashes, "
578
+ "and optionally paste the exact prompts/answers to verify every receipt’s SHA/HMAC in one click."
579
+ )
580
+
581
+ sid_in = gr.Textbox(label="Session ID (leave blank = latest)")
582
+ load_btn = gr.Button("Load & Recompute")
583
+ comp_root = gr.Textbox(label="Recomputed Merkle root", interactive=False)
584
+ root_match = gr.Textbox(label="Root match", interactive=False)
585
+ table = gr.Dataframe(headers=["receipt_id", "session_leaf", "recomputed_leaf", "match"],
586
+ interactive=False, wrap=True)
587
+
588
+ proof_link2 = gr.Markdown()
589
+
590
+ def ui_load_and_recompute(sid_text: str):
591
+ sid = sid_text.strip() or latest_session_id()
592
+ if not sid:
593
+ return "", "No sessions found", [], ""
594
+ comp, msg, rows, link = recompute_session_root_and_table(sid)
595
+ return comp, msg, rows, link
596
+
597
+ load_btn.click(ui_load_and_recompute, inputs=sid_in, outputs=[comp_root, root_match, table, proof_link2])
598
+
599
+ gr.Markdown("### Per-turn text verification (paste the exact prompts & answers)")
600
+ pepper_tb = gr.Textbox(label="AUDIT_PEPPER (optional; if set in Space secrets, paste the same value)")
601
+ prep_btn = gr.Button("Prepare rows for this session")
602
+ rows_df = gr.Dataframe(headers=["receipt_id","prompt","answer","sha_ok","hmac_ok"],
603
+ row_count=(0, "dynamic"), datatype=["str","str","str","bool","bool"])
604
+
605
+ verify_btn = gr.Button("Verify pasted texts for all receipts")
606
+ rows_out = gr.Dataframe(headers=["receipt_id","prompt","answer","sha_ok","hmac_ok"],
607
+ row_count=(0, "dynamic"), datatype=["str","str","str","bool","bool"])
608
+
609
+ def ui_prep_rows(sid_text: str):
610
+ sid = sid_text.strip() or latest_session_id()
611
+ if not sid:
612
+ return []
613
+ return make_text_rows_for_session(sid)
614
+
615
+ def ui_verify_rows(sid_text: str, rows, pepper):
616
+ sid = sid_text.strip() or latest_session_id()
617
+ if not sid:
618
+ return rows or []
619
+ return verify_text_rows(sid, rows, pepper or "")
620
+
621
+ prep_btn.click(ui_prep_rows, inputs=sid_in, outputs=rows_df)
622
+ verify_btn.click(ui_verify_rows, inputs=[sid_in, rows_df, pepper_tb], outputs=rows_out)
623
+
624
  # Expose the ASGI app for Spaces (Docker or Gradio SDK)
625
  demo = api_ui
626
  app = gr.mount_gradio_app(api, api_ui, path="/")