RFTSystems commited on
Commit
57bc670
·
verified ·
1 Parent(s): bdba6f2

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +262 -0
app.py ADDED
@@ -0,0 +1,262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ from difflib import unified_diff
4
+
5
+ import gradio as gr
6
+
7
+ import rft_flightrecorder as fr
8
+
9
+ LOG_PATH = fr.DEFAULT_LOG_PATH
10
+
11
+
12
+ def text_diff(before_text: str, after_text: str):
13
+ a = (before_text or "").splitlines()
14
+ b = (after_text or "").splitlines()
15
+ diff = list(unified_diff(a, b, fromfile="before", tofile="after", lineterm=""))
16
+ return "\n".join(diff) if diff else "(no differences)"
17
+
18
+
19
+ def download_log():
20
+ return LOG_PATH if os.path.exists(LOG_PATH) else None
21
+
22
+
23
+ def ui_start_session(model_id, run_mode, notes, sign_start, sk_hex):
24
+ sid, msg = fr.start_session(LOG_PATH, model_id, run_mode, notes, sign_start, sk_hex)
25
+ # fan-out the session id into all tabs
26
+ return sid, sid, sid, sid, sid, msg
27
+
28
+
29
+ def ui_append_event(session_id, event_type, parent_hash, payload_text, sign_event, sk_hex, model_id, run_mode):
30
+ ev, msg = fr.append_event(
31
+ log_path=LOG_PATH,
32
+ session_id=session_id,
33
+ event_type=event_type,
34
+ payload_text=payload_text,
35
+ parent_event_hash=parent_hash,
36
+ sign_event=sign_event,
37
+ sk_hex=sk_hex,
38
+ model_id=model_id,
39
+ run_mode=run_mode,
40
+ )
41
+ return ev, msg
42
+
43
+
44
+ def ui_timeline(session_id):
45
+ rows, msg = fr.session_timeline_rows(LOG_PATH, session_id)
46
+ return rows, msg
47
+
48
+
49
+ def ui_verify(session_id, pk_hex, require_sigs):
50
+ return fr.verify_session(LOG_PATH, session_id, pk_hex, require_sigs)
51
+
52
+
53
+ def ui_finalise(session_id, sign_anchor, sk_hex, model_id, run_mode):
54
+ return fr.finalise_session(LOG_PATH, session_id, sign_anchor, sk_hex, model_id, run_mode)
55
+
56
+
57
+ def ui_export(session_id):
58
+ return fr.export_session_bundle(LOG_PATH, session_id)
59
+
60
+
61
+ def ui_list_sessions():
62
+ sessions, msg = fr.list_sessions(LOG_PATH)
63
+ return gr.Dropdown(choices=sessions, value=(sessions[-1] if sessions else None)), msg
64
+
65
+
66
+ def ui_pick_session(sid):
67
+ sid = (sid or "").strip()
68
+ return sid, sid, sid, sid, sid
69
+
70
+
71
+ def ui_get_event(session_id, ev_hash):
72
+ return fr.get_event_by_hash(LOG_PATH, session_id, ev_hash)
73
+
74
+
75
+ def ui_import_bundle(bundle_file, pk_hex, require_sigs, store_into_log):
76
+ # gr.File returns a path string
77
+ status, ok, report, stored_msg = fr.import_bundle_verify(
78
+ bundle_path=bundle_file,
79
+ pk_hex=pk_hex,
80
+ require_signatures=require_sigs,
81
+ store_into_log=store_into_log,
82
+ log_path=LOG_PATH,
83
+ )
84
+ extra = (stored_msg or "")
85
+ return status, ok, report + (("\n\n" + extra) if extra else "")
86
+
87
+
88
+ with gr.Blocks(title="RFT Agent Flight Recorder — Black Box Trace + Third-Party Verification") as demo:
89
+ gr.Markdown(
90
+ "# RFT Agent Flight Recorder — Black Box Trace + Third-Party Verification\n"
91
+ "This Space records a **tamper-evident, hash-chained event timeline** for AI/agent runs.\n\n"
92
+ "**Core deliverable:** export a ZIP bundle that **anyone can verify**.\n\n"
93
+ "**Key safety note:** Public demo. Do not paste production private keys here."
94
+ )
95
+
96
+ with gr.Tab("Keys"):
97
+ sk = gr.Textbox(label="Ed25519 Private Key (hex)", lines=2)
98
+ pk = gr.Textbox(label="Ed25519 Public Key (hex)", lines=2)
99
+ gen = gr.Button("Generate Keypair")
100
+ gen.click(fn=fr.gen_keys, outputs=[sk, pk])
101
+
102
+ with gr.Tab("Sessions"):
103
+ with gr.Row():
104
+ list_btn = gr.Button("Refresh session list")
105
+ sessions_dd = gr.Dropdown(label="Existing sessions", choices=[], value=None)
106
+ sessions_msg = gr.Textbox(label="Status", lines=1)
107
+
108
+ sid_start = gr.Textbox(label="session_id (Start/Record)", lines=1)
109
+ sid_tl = gr.Textbox(label="session_id (Timeline)", lines=1)
110
+ sid_verify = gr.Textbox(label="session_id (Verify)", lines=1)
111
+ sid_final = gr.Textbox(label="session_id (Finalise/Export)", lines=1)
112
+ sid_record = gr.Textbox(label="session_id (Record Event)", lines=1)
113
+
114
+ list_btn.click(fn=ui_list_sessions, outputs=[sessions_dd, sessions_msg])
115
+ sessions_dd.change(fn=ui_pick_session, inputs=[sessions_dd], outputs=[sid_start, sid_record, sid_tl, sid_verify, sid_final])
116
+
117
+ with gr.Tab("Start Session"):
118
+ model_id = gr.Textbox(label="Model ID", value="audit-demo")
119
+ run_mode = gr.Radio(["deterministic", "creative"], label="Run mode", value="deterministic")
120
+ notes = gr.Textbox(label="Notes (optional)", lines=3)
121
+ sign_start = gr.Checkbox(label="Sign session_start event", value=False)
122
+ start_btn = gr.Button("Start New Session")
123
+ start_status = gr.Textbox(label="Status", lines=2)
124
+
125
+ # fan-out session id to all places
126
+ start_btn.click(
127
+ fn=ui_start_session,
128
+ inputs=[model_id, run_mode, notes, sign_start, sk],
129
+ outputs=[sid_start, sid_record, sid_tl, sid_verify, sid_final, start_status],
130
+ )
131
+
132
+ with gr.Tab("Record Event"):
133
+ event_type = gr.Dropdown(
134
+ choices=[
135
+ "prompt",
136
+ "output",
137
+ "tool_call",
138
+ "tool_result",
139
+ "memory_read",
140
+ "memory_write",
141
+ "retrieval",
142
+ "policy_block",
143
+ "error",
144
+ "note",
145
+ ],
146
+ value="note",
147
+ label="event_type",
148
+ )
149
+ parent_hash = gr.Textbox(label="parent_event_hash_sha256 (optional). If empty, defaults to previous event.", lines=1)
150
+ payload_text = gr.Textbox(
151
+ label="payload (JSON or plain text)",
152
+ lines=10,
153
+ placeholder='Example JSON:\n{\n "tool":"search",\n "input":{"q":"..."},\n "output":{"items":[...]}\n}\n',
154
+ )
155
+ sign_event = gr.Checkbox(label="Sign this event (Ed25519)", value=False)
156
+ append_btn = gr.Button("Append Event")
157
+ event_out = gr.JSON(label="event.json")
158
+ append_status = gr.Textbox(label="Status", lines=2)
159
+
160
+ append_btn.click(
161
+ fn=ui_append_event,
162
+ inputs=[sid_record, event_type, parent_hash, payload_text, sign_event, sk, model_id, run_mode],
163
+ outputs=[event_out, append_status],
164
+ )
165
+
166
+ flightlog_file = gr.File(label="flightlog.jsonl (download)")
167
+ gr.Button("Download flightlog.jsonl").click(fn=download_log, outputs=[flightlog_file])
168
+
169
+ with gr.Tab("Timeline"):
170
+ refresh = gr.Button("Load timeline")
171
+ tl_status = gr.Textbox(label="Status", lines=1)
172
+ tl = gr.Dataframe(
173
+ headers=[
174
+ "seq",
175
+ "ts_utc",
176
+ "event_type",
177
+ "model_id",
178
+ "run_mode",
179
+ "parent_hash",
180
+ "prev_hash",
181
+ "event_hash",
182
+ "signed",
183
+ ],
184
+ datatype=["number", "str", "str", "str", "str", "str", "str", "str", "str"],
185
+ row_count=10,
186
+ col_count=(9, "fixed"),
187
+ label="Event timeline",
188
+ wrap=True,
189
+ )
190
+
191
+ refresh.click(fn=ui_timeline, inputs=[sid_tl], outputs=[tl, tl_status])
192
+
193
+ with gr.Accordion("View event by hash", open=False):
194
+ ev_hash_in = gr.Textbox(label="event_hash_sha256", lines=1)
195
+ ev_get = gr.Button("Get event")
196
+ ev_status = gr.Textbox(label="Status", lines=1)
197
+ ev_json = gr.JSON(label="event.json")
198
+ ev_get.click(fn=ui_get_event, inputs=[sid_tl, ev_hash_in], outputs=[ev_json, ev_status])
199
+
200
+ with gr.Tab("Verify Session"):
201
+ require_sigs = gr.Checkbox(label="Require signatures on every event", value=False)
202
+ verify_btn = gr.Button("Verify")
203
+ verify_msg = gr.Textbox(label="Result", lines=1)
204
+ verify_ok = gr.Checkbox(label="Valid", value=False)
205
+ verify_report = gr.Textbox(label="Report", lines=14)
206
+
207
+ verify_btn.click(
208
+ fn=ui_verify,
209
+ inputs=[sid_verify, pk, require_sigs],
210
+ outputs=[verify_msg, verify_ok, verify_report],
211
+ )
212
+
213
+ with gr.Tab("Finalise + Export"):
214
+ sign_anchor = gr.Checkbox(label="Sign session anchor + session_end event", value=False)
215
+ fin_btn = gr.Button("Finalise session")
216
+ anchor_out = gr.JSON(label="session_anchor.json")
217
+ fin_status = gr.Textbox(label="Status", lines=2)
218
+
219
+ fin_btn.click(
220
+ fn=ui_finalise,
221
+ inputs=[sid_final, sign_anchor, sk, model_id, run_mode],
222
+ outputs=[anchor_out, fin_status],
223
+ )
224
+
225
+ export_btn = gr.Button("Export session bundle (ZIP)")
226
+ export_status = gr.Textbox(label="Export status", lines=1)
227
+ export_file = gr.File(label="Bundle download")
228
+
229
+ export_btn.click(
230
+ fn=ui_export,
231
+ inputs=[sid_final],
232
+ outputs=[export_file, export_status],
233
+ )
234
+
235
+ with gr.Tab("Import Bundle"):
236
+ bundle = gr.File(label="Upload rft_flight_bundle_*.zip")
237
+ store_into_log = gr.Checkbox(label="Store imported events into local flightlog.jsonl (only if PASS)", value=False)
238
+ import_require_sigs = gr.Checkbox(label="Require signatures on every imported event", value=False)
239
+ import_btn = gr.Button("Verify bundle")
240
+ import_status = gr.Textbox(label="Result", lines=1)
241
+ import_ok = gr.Checkbox(label="Valid", value=False)
242
+ import_report = gr.Textbox(label="Report", lines=14)
243
+
244
+ import_btn.click(
245
+ fn=ui_import_bundle,
246
+ inputs=[bundle, pk, import_require_sigs, store_into_log],
247
+ outputs=[import_status, import_ok, import_report],
248
+ )
249
+
250
+ with gr.Tab("Diagnostics"):
251
+ diag_btn = gr.Button("Run diagnostics")
252
+ diag_out = gr.JSON(label="diagnostics.json")
253
+ diag_btn.click(fn=lambda: fr.diagnostics(LOG_PATH), outputs=[diag_out])
254
+
255
+ with gr.Tab("Diff Helper"):
256
+ before = gr.Textbox(label="Before (text/JSON)", lines=8)
257
+ after = gr.Textbox(label="After (text/JSON)", lines=8)
258
+ diff_btn = gr.Button("Generate unified diff")
259
+ diff_out = gr.Textbox(label="Diff", lines=14)
260
+ diff_btn.click(fn=text_diff, inputs=[before, after], outputs=[diff_out])
261
+
262
+ demo.launch()