thundercode commited on
Commit
93a048a
·
verified ·
1 Parent(s): ccd0ae9

release: add tools/verify_archive.py

Browse files
Files changed (1) hide show
  1. tools/verify_archive.py +136 -0
tools/verify_archive.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Verify the SatQuery AI evidence archive (Phase 8 — HARD GATE).
2
+
3
+ Extracts the archive to a SEPARATE temp directory (never over the originals), then checks:
4
+ 1. zipfile integrity (testzip + CRC of every member)
5
+ 2. entry count matches the manifest of members
6
+ 3. every artifact under artifacts/ is present and byte-identical (sha256) to the live file
7
+ 4. the curated release tree is present with its key files
8
+ 5. the six released model weights are byte-identical to the live files
9
+
10
+ Exit 0 only if every check passes. If it fails, the caller MUST NOT proceed to deletion.
11
+ """
12
+ import hashlib
13
+ import json
14
+ import os
15
+ import shutil
16
+ import sys
17
+ import tempfile
18
+ import zipfile
19
+
20
+ WORKSPACE = r"C:/Users/anish/WorkBuddy AI/2026-09-25-21-52-59"
21
+ SRC_REPO = r"C:/Users/anish/satquery-ai"
22
+ STAMP = "2026-09-25"
23
+ ROOT = f"SatQuery_AI_Final_Archive_{STAMP}"
24
+ ARCHIVE = os.path.join(WORKSPACE, f"{ROOT}.zip")
25
+
26
+
27
+ def sha256_of(path, chunk=1 << 20):
28
+ h = hashlib.sha256()
29
+ with open(path, "rb") as fh:
30
+ while True:
31
+ b = fh.read(chunk)
32
+ if not b:
33
+ break
34
+ h.update(b)
35
+ return h.hexdigest()
36
+
37
+
38
+ def main():
39
+ if not os.path.exists(ARCHIVE):
40
+ print(f"ERROR: archive not found: {ARCHIVE}")
41
+ return 2
42
+
43
+ print(f"archive : {ARCHIVE}")
44
+ print(f"size : {os.path.getsize(ARCHIVE):,} bytes")
45
+ print()
46
+
47
+ failures = []
48
+
49
+ # --- 1. integrity -----------------------------------------------------
50
+ with zipfile.ZipFile(ARCHIVE) as zf:
51
+ names = zf.namelist()
52
+ print(f"entries : {len(names)}")
53
+ bad = zf.testzip()
54
+ if bad is not None:
55
+ failures.append(f"CRC failure in member: {bad}")
56
+ print(f" CRC check : FAIL ({bad})")
57
+ else:
58
+ print(" CRC check : PASS (every member CRC verified)")
59
+
60
+ # --- 2. extract to a SEPARATE temp dir ----------------------------
61
+ tmp = tempfile.mkdtemp(prefix="sq_archive_verify_")
62
+ print(f" extracting to: {tmp}")
63
+ zf.extractall(tmp)
64
+
65
+ extracted_root = os.path.join(tmp, ROOT)
66
+
67
+ # --- 3. artifacts byte-identity --------------------------------------
68
+ print()
69
+ print("artifact byte-identity (extracted vs live):")
70
+ art_src = os.path.join(SRC_REPO, "artifacts")
71
+ art_ext = os.path.join(extracted_root, "artifacts")
72
+ n_checked = n_ok = 0
73
+ for dirpath, dirnames, filenames in os.walk(art_src):
74
+ dirnames[:] = [d for d in dirnames if d not in {"__pycache__", ".git"}]
75
+ for fn in filenames:
76
+ if fn.endswith((".pyc", ".pyo")):
77
+ continue
78
+ full = os.path.join(dirpath, fn)
79
+ rel = os.path.relpath(full, art_src)
80
+ ext = os.path.join(art_ext, rel)
81
+ n_checked += 1
82
+ if not os.path.exists(ext):
83
+ failures.append(f"missing in archive: artifacts/{rel}")
84
+ continue
85
+ if os.path.getsize(full) != os.path.getsize(ext) or sha256_of(full) != sha256_of(ext):
86
+ failures.append(f"byte mismatch: artifacts/{rel}")
87
+ else:
88
+ n_ok += 1
89
+ print(f" artifacts checked : {n_checked}")
90
+ print(f" byte-identical : {n_ok}")
91
+
92
+ # --- 4. release tree --------------------------------------------------
93
+ print()
94
+ print("curated release tree:")
95
+ for rel in ["README.md", "MODEL_CARD.md", "models/manifest.json", "models/checksums.sha256"]:
96
+ p = os.path.join(extracted_root, "release", rel)
97
+ ok = os.path.exists(p)
98
+ print(f" {'OK ' if ok else 'MISS'} release/{rel}")
99
+ if not ok:
100
+ failures.append(f"missing in archive: release/{rel}")
101
+
102
+ # --- 5. the six released weights -------------------------------------
103
+ print()
104
+ print("six released model weights (extracted vs live):")
105
+ manifest = json.load(
106
+ open(os.path.join(WORKSPACE, "release", "repo", "models", "manifest.json"), encoding="utf-8")
107
+ )
108
+ for a in manifest["artifacts"]:
109
+ live = os.path.join(SRC_REPO, a["path"])
110
+ ext = os.path.join(extracted_root, "artifacts", os.path.relpath(a["path"], "artifacts"))
111
+ if not os.path.exists(ext):
112
+ failures.append(f"missing weight in archive: {a['path']}")
113
+ print(f" MISS {a['id']}")
114
+ continue
115
+ same = sha256_of(live) == sha256_of(ext)
116
+ print(f" {'OK ' if same else 'BAD '} {a['id']}")
117
+ if not same:
118
+ failures.append(f"weight byte mismatch: {a['path']}")
119
+
120
+ # --- cleanup temp -----------------------------------------------------
121
+ shutil.rmtree(tmp, ignore_errors=True)
122
+
123
+ print()
124
+ if failures:
125
+ print(f"ARCHIVE VERIFICATION: FAILED ({len(failures)} problem(s))")
126
+ for f in failures[:50]:
127
+ print(" - " + f)
128
+ print()
129
+ print("DO NOT PROCEED TO DELETION.")
130
+ return 1
131
+ print("ARCHIVE VERIFICATION: PASSED")
132
+ return 0
133
+
134
+
135
+ if __name__ == "__main__":
136
+ sys.exit(main())