RFTSystems commited on
Commit
bc1bcc0
·
verified ·
1 Parent(s): d42cde7

Create drp/bundle.py

Browse files
Files changed (1) hide show
  1. drp/bundle.py +142 -0
drp/bundle.py ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import json
3
+ import os
4
+ import zipfile
5
+ from dataclasses import dataclass
6
+ from typing import Any, Dict, List, Optional, Tuple
7
+
8
+ from .canon import DRP_BUNDLE_SPEC, DRP_EVENT_SPEC, canon, hash_event, now_utc_iso
9
+
10
+
11
+ @dataclass
12
+ class Bundle:
13
+ manifest: Dict[str, Any]
14
+ events: List[Dict[str, Any]]
15
+
16
+
17
+ def _read_json_from_zip(z: zipfile.ZipFile, name: str) -> Dict[str, Any]:
18
+ with z.open(name, "r") as f:
19
+ return json.loads(f.read().decode("utf-8"))
20
+
21
+
22
+ def _read_jsonl_from_zip(z: zipfile.ZipFile, name: str) -> List[Dict[str, Any]]:
23
+ out: List[Dict[str, Any]] = []
24
+ with z.open(name, "r") as f:
25
+ for line in f.read().decode("utf-8").splitlines():
26
+ line = line.strip()
27
+ if not line:
28
+ continue
29
+ out.append(json.loads(line))
30
+ return out
31
+
32
+
33
+ def load_bundle(zip_path: str) -> Bundle:
34
+ with zipfile.ZipFile(zip_path, "r") as z:
35
+ manifest = _read_json_from_zip(z, "manifest.json")
36
+ events = _read_jsonl_from_zip(z, "events.jsonl")
37
+ return Bundle(manifest=manifest, events=events)
38
+
39
+
40
+ def verify_bundle(zip_path: str) -> Tuple[bool, Dict[str, Any]]:
41
+ """
42
+ Verifies:
43
+ - bundle spec fields exist
44
+ - each event has correct hash
45
+ - hash chain prev pointers match
46
+ """
47
+ b = load_bundle(zip_path)
48
+
49
+ issues: List[str] = []
50
+ if b.manifest.get("spec") != DRP_BUNDLE_SPEC:
51
+ issues.append(f"manifest.spec mismatch (expected {DRP_BUNDLE_SPEC})")
52
+
53
+ events = b.events
54
+ if not events:
55
+ issues.append("no events found")
56
+ return (False, {"ok": False, "issues": issues})
57
+
58
+ # verify event specs + hashes + chain
59
+ prev_hash: Optional[str] = None
60
+ for idx, ev in enumerate(events):
61
+ if ev.get("spec") != DRP_EVENT_SPEC:
62
+ issues.append(f"event[{idx}].spec mismatch (expected {DRP_EVENT_SPEC})")
63
+
64
+ computed = hash_event(ev)
65
+ if ev.get("hash") != computed:
66
+ issues.append(f"event[{idx}] hash mismatch")
67
+
68
+ if idx == 0:
69
+ # first event may have prev = None / "" or absent
70
+ pass
71
+ else:
72
+ if ev.get("prev") != prev_hash:
73
+ issues.append(f"event[{idx}] prev pointer mismatch")
74
+
75
+ prev_hash = ev.get("hash")
76
+
77
+ ok = len(issues) == 0
78
+ summary = {
79
+ "ok": ok,
80
+ "issues": issues,
81
+ "event_count": len(events),
82
+ "run_id": b.manifest.get("run_id"),
83
+ "created_at": b.manifest.get("created_at"),
84
+ "framework": b.manifest.get("framework"),
85
+ }
86
+ return (ok, summary)
87
+
88
+
89
+ def write_bundle_zip(
90
+ out_zip_path: str,
91
+ *,
92
+ run_id: str,
93
+ framework: str,
94
+ model_id: str,
95
+ env_fingerprint: Dict[str, Any],
96
+ events_payloads: List[Dict[str, Any]],
97
+ created_at: Optional[str] = None,
98
+ ) -> str:
99
+ """
100
+ Creates a DRP bundle zip:
101
+ - manifest.json
102
+ - events.jsonl (hash-chained)
103
+ """
104
+ created_at = created_at or now_utc_iso()
105
+
106
+ manifest = {
107
+ "spec": DRP_BUNDLE_SPEC,
108
+ "run_id": run_id,
109
+ "created_at": created_at,
110
+ "framework": framework,
111
+ "model_id": model_id,
112
+ "env": env_fingerprint,
113
+ }
114
+
115
+ events: List[Dict[str, Any]] = []
116
+ prev_hash: Optional[str] = None
117
+ for i, payload in enumerate(events_payloads):
118
+ ev = {
119
+ "spec": DRP_EVENT_SPEC,
120
+ "i": i,
121
+ "ts": payload.get("ts") or now_utc_iso(),
122
+ "kind": payload.get("kind", "state_snapshot"),
123
+ "step": payload.get("step", f"step-{i}"),
124
+ "payload": payload.get("payload", {}),
125
+ "prev": prev_hash,
126
+ }
127
+ ev["hash"] = hash_event(ev)
128
+ prev_hash = ev["hash"]
129
+ events.append(ev)
130
+
131
+ # write zip
132
+ os.makedirs(os.path.dirname(out_zip_path) or ".", exist_ok=True)
133
+ with zipfile.ZipFile(out_zip_path, "w", compression=zipfile.ZIP_DEFLATED) as z:
134
+ z.writestr("manifest.json", json.dumps(manifest, ensure_ascii=False, indent=2))
135
+ # jsonl
136
+ buf = io.StringIO()
137
+ for ev in events:
138
+ buf.write(json.dumps(ev, ensure_ascii=False, separators=(",", ":")))
139
+ buf.write("\n")
140
+ z.writestr("events.jsonl", buf.getvalue())
141
+
142
+ return out_zip_path