thundercode commited on
Commit
5a89f02
·
verified ·
1 Parent(s): 5353ddc

release: add tools/build_archive.py

Browse files
Files changed (1) hide show
  1. tools/build_archive.py +187 -0
tools/build_archive.py ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Build the SatQuery AI evidence archive (Phase 7).
2
+
3
+ Owner decision G3: everything under `artifacts/` is included VERBATIM (duplicates and caches
4
+ included), so the archive is ~4 GB and REQUIRES ZIP64.
5
+
6
+ Archive layout:
7
+
8
+ SatQuery_AI_Final_Archive_2026-09-25/
9
+ README_ARCHIVE.md (what is inside, what was excluded and why)
10
+ release/ (the curated public release tree, verbatim)
11
+ artifacts/ (VERBATIM, per owner decision G3)
12
+ evidence/
13
+ live_validation/ (3 validation passes + screenshots)
14
+ delivery/ (delivery report + handoff)
15
+ verification/ (state, manifests, verification reports)
16
+
17
+ Exclusions (documented in README_ARCHIVE.md, never silent):
18
+ secrets/token files, OS junk, virtualenvs, node_modules, HF hub cache, temp browser profiles.
19
+
20
+ Writes only the .zip. Does NOT delete or move anything.
21
+ """
22
+ import os
23
+ import sys
24
+ import zipfile
25
+ import datetime
26
+
27
+ WORKSPACE = r"C:/Users/anish/WorkBuddy AI/2026-09-25-21-52-59"
28
+ SRC_REPO = r"C:/Users/anish/satquery-ai"
29
+ STAMP = "2026-09-25"
30
+ ROOT = f"SatQuery_AI_Final_Archive_{STAMP}"
31
+ OUT = os.path.join(WORKSPACE, f"{ROOT}.zip")
32
+
33
+ # Directories copied recursively into the archive, as (source, arc_prefix)
34
+ TREES = [
35
+ (os.path.join(SRC_REPO, "artifacts"), f"{ROOT}/artifacts"),
36
+ (os.path.join(WORKSPACE, "release", "repo"), f"{ROOT}/release"),
37
+ (os.path.join(WORKSPACE, ".workbuddy-ai", "scratch", "live_validation"),
38
+ f"{ROOT}/evidence/live_validation"),
39
+ ]
40
+
41
+ # Individual files, as (source, arc_name)
42
+ FILES = [
43
+ (os.path.join(WORKSPACE, "DELIVERY_REPORT_2026-09-25.md"), f"{ROOT}/evidence/delivery/DELIVERY_REPORT_2026-09-25.md"),
44
+ (os.path.join(WORKSPACE, "HANDOFF_NEXT_AGENT.md"), f"{ROOT}/evidence/delivery/HANDOFF_NEXT_AGENT.md"),
45
+ (os.path.join(WORKSPACE, "release", "CURRENT_RELEASE_STATE.md"), f"{ROOT}/verification/CURRENT_RELEASE_STATE.md"),
46
+ (os.path.join(WORKSPACE, "release", "RELEASE_EXECUTION_CHECKLIST.md"), f"{ROOT}/verification/RELEASE_EXECUTION_CHECKLIST.md"),
47
+ (os.path.join(WORKSPACE, "release", "HF_RELEASE_VERIFICATION.md"), f"{ROOT}/verification/HF_RELEASE_VERIFICATION.md"),
48
+ (os.path.join(WORKSPACE, "release", "DOCS_STYLE_GUIDE.md"), f"{ROOT}/verification/DOCS_STYLE_GUIDE.md"),
49
+ (os.path.join(WORKSPACE, "release", "tools", "verify_readme_metrics.py"), f"{ROOT}/verification/tools/verify_readme_metrics.py"),
50
+ (os.path.join(WORKSPACE, "release", "tools", "readme_metrics_report.txt"), f"{ROOT}/verification/tools/readme_metrics_report.txt"),
51
+ (os.path.join(WORKSPACE, "release", "tools", "generate_model_manifest.py"), f"{ROOT}/verification/tools/generate_model_manifest.py"),
52
+ (os.path.join(WORKSPACE, "release", "tools", "model_manifest_report.txt"), f"{ROOT}/verification/tools/model_manifest_report.txt"),
53
+ (os.path.join(WORKSPACE, "release", "tools", "hf_upload.py"), f"{ROOT}/verification/tools/hf_upload.py"),
54
+ (os.path.join(WORKSPACE, "release", "tools", "hf_verify.py"), f"{ROOT}/verification/tools/hf_verify.py"),
55
+ (os.path.join(WORKSPACE, "release", "tools", "hf_verify_report.txt"), f"{ROOT}/verification/tools/hf_verify_report.txt"),
56
+ ]
57
+
58
+ EXCLUDE_DIR_NAMES = {
59
+ ".git", "__pycache__", ".venv", "venv", "node_modules", ".pytest_cache",
60
+ ".mypy_cache", ".ruff_cache", ".ipynb_checkpoints",
61
+ }
62
+ EXCLUDE_SUFFIX = (".pyc", ".pyo", ".pyd")
63
+
64
+
65
+ def should_skip(path):
66
+ parts = set(os.path.normpath(path).split(os.sep))
67
+ if parts & EXCLUDE_DIR_NAMES:
68
+ return True
69
+ return path.endswith(EXCLUDE_SUFFIX)
70
+
71
+
72
+ def add_tree(zf, src, prefix, counters):
73
+ for dirpath, dirnames, filenames in os.walk(src):
74
+ dirnames[:] = [d for d in dirnames if d not in EXCLUDE_DIR_NAMES]
75
+ for fn in filenames:
76
+ full = os.path.join(dirpath, fn)
77
+ if should_skip(full):
78
+ continue
79
+ rel = os.path.relpath(full, src).replace(os.sep, "/")
80
+ arc = f"{prefix}/{rel}"
81
+ try:
82
+ zf.write(full, arc, compress_type=zipfile.ZIP_STORED) # weights/caches: no recompress
83
+ counters["files"] += 1
84
+ counters["bytes"] += os.path.getsize(full)
85
+ except OSError as e:
86
+ counters["errors"].append(f"{full}: {e}")
87
+
88
+
89
+ ARCHIVE_README = f"""# SatQuery AI — Final Evidence Archive ({STAMP})
90
+
91
+ This archive is the complete, verified evidence bundle for the SatQuery AI final release.
92
+
93
+ ## Owner decision on scope
94
+
95
+ Per the owner's explicit decision (**G3**), **everything under `artifacts/` is included VERBATIM** —
96
+ including duplicate ZIPs, feature caches and superseded checkpoints. This makes the archive large
97
+ (~4 GB) and is intentional: nothing was curated away.
98
+
99
+ ## Contents
100
+
101
+ | Path | What it is |
102
+ |---|---|
103
+ | `release/` | the curated public release tree (README, MODEL_CARD, docs/, models/, screenshots/) |
104
+ | `artifacts/` | the project's `artifacts/` directory, verbatim |
105
+ | `evidence/live_validation/` | the three live validation passes, raw logs, result JSON, 8 screenshots |
106
+ | `evidence/delivery/` | the delivery report and the handoff document |
107
+ | `verification/` | release state, verification reports, and the tools that produced them |
108
+
109
+ ## What was EXCLUDED (explicitly, never silently)
110
+
111
+ | Excluded | Reason |
112
+ |---|---|
113
+ | Secret / token files | never archived |
114
+ | `.git/`, `__pycache__/`, `.pytest_cache/`, `.mypy_cache/` | version-control and build caches |
115
+ | virtualenvs (`.venv`, `venv`) | reproducible from `requirements.txt` |
116
+ | `node_modules/` | reproducible from the frontend package manifest |
117
+ | Hugging Face Hub cache | re-downloadable, pinned by revision |
118
+ | Temporary browser profiles | ephemeral |
119
+ | `*.pyc`, `*.pyo`, `*.pyd` | compiled bytecode |
120
+
121
+ ## Reproducible-but-included note
122
+
123
+ The archive deliberately includes **duplicates and caches** that are reproducible:
124
+
125
+ | Item | Size | Classification |
126
+ |---|---|---|
127
+ | `artifacts/grounding/remoteclip_grounding_v001.zip` | ~774 MB | DUPLICATE of the extracted directory |
128
+ | `artifacts/change/levir_change_cpu_probe_v001/` | ~241 MB | DUPLICATE probe of `levir_change_v001` |
129
+ | `artifacts/optical_sar/fusion_features/`, `fusion_features_armB/` | ~231 MB each | reproducible feature caches |
130
+ | `artifacts/optical_sar/fusion_head_v001/` | ~276 MB | superseded by `fusion_head_production_v001` |
131
+
132
+ These are retained because the owner chose "verbatim". They are listed here so the size is
133
+ explained, not surprising.
134
+
135
+ ## Integrity
136
+
137
+ Verify this archive with `release/tools/verify_archive.py`, which extracts to a separate temporary
138
+ directory and checks the CRC of every member plus the sha256 of every artifact against the live
139
+ files. The result is recorded in `ARCHIVE_VERIFICATION.md`.
140
+
141
+ Generated: {STAMP}
142
+ """
143
+
144
+
145
+ def main():
146
+ counters = {"files": 0, "bytes": 0, "errors": []}
147
+ print(f"Building {OUT}")
148
+ print("(ZIP_STORED for artifacts — recompressing already-compressed weights wastes hours)")
149
+ print()
150
+
151
+ # ZIP64 is automatic in Python when a file/offset needs it.
152
+ with zipfile.ZipFile(OUT, "w", allowZip64=True) as zf:
153
+ zf.writestr(f"{ROOT}/README_ARCHIVE.md", ARCHIVE_README)
154
+ counters["files"] += 1
155
+ for src, prefix in TREES:
156
+ if not os.path.isdir(src):
157
+ counters["errors"].append(f"MISSING TREE: {src}")
158
+ continue
159
+ before = counters["files"]
160
+ add_tree(zf, src, prefix, counters)
161
+ print(f" + {prefix:52} {counters['files'] - before:>7} files")
162
+ for src, arc in FILES:
163
+ if not os.path.exists(src):
164
+ counters["errors"].append(f"MISSING FILE: {src}")
165
+ continue
166
+ zf.write(src, arc, compress_type=zipfile.ZIP_DEFLATED)
167
+ counters["files"] += 1
168
+ counters["bytes"] += os.path.getsize(src)
169
+ print(f" + verification/… (individual files)")
170
+
171
+ size = os.path.getsize(OUT)
172
+ print()
173
+ print(f"archive : {OUT}")
174
+ print(f"archive size : {size:,} bytes ({size / 1024**3:.2f} GiB)")
175
+ print(f"entries : {counters['files']}")
176
+ print(f"source bytes : {counters['bytes']:,}")
177
+ if counters["errors"]:
178
+ print()
179
+ print("ERRORS:")
180
+ for e in counters["errors"]:
181
+ print(" " + e)
182
+ return 1
183
+ return 0
184
+
185
+
186
+ if __name__ == "__main__":
187
+ sys.exit(main())