diff --git a/docs/perf/winv2/README.md b/docs/perf/winv2/README.md
index 40a3f702..616be79f 100644
--- a/docs/perf/winv2/README.md
+++ b/docs/perf/winv2/README.md
@@ -138,7 +138,31 @@ Takeaways: the archive (Bug #2 fix) turns a broken/∞ first-app into a working
SAME Defender-on-many-small-files cost as cold app-startup (Task #9) -- the one
lever that would shrink both.
-### Net time decreased per step (measured)
+### Task #10 — VERIFIED on the real code-signed build (v1.3.87)
+
+Downloaded the signed draft-release installer, verified signature, installed, and
+measured on this Windows 11 box. All numbers are from the packaged app, not dev.
+
+| metric | baseline (1.2.x) | signed v1.3.87 | result |
+| --- | --- | --- | --- |
+| installer download | n/a | 371.5 MB @ 23.5 MB/s (15.8 s) | signed: Authenticode **Valid** (CN=Eric Zeng) |
+| install time | n/a | ~9.3 s | Squirrel |
+| **cold backend-http-ready** | **54-138 s** | **22.5 s** | **~75-84% faster** |
+| **warm backend-http-ready** | **9-10 s** | **5.0 s** | **~50% faster, under the 10s goal** |
+| app.asar size | ~607 MB | **2.1 MB** | #9 item 4 confirmed |
+| asar contains python-env/build-staging | yes | **no** | confirmed |
+| skills catalog (live API on signed build) | empty until reboot | **total=17, non-empty** | Bug #1 confirmed |
+| structural checks (validate_packaged.ps1) | 4 fail | **5/5 PASS** | snapshot + node tar + unpacked python-env |
+
+Cold is 22.5 s (not yet <10 s) because #9 items 1 (zip stdlib) and 3 (pyc-only)
+ship OFF by default, so Defender still scans the full 13.5k-file python-env on the
+first post-update launch. Enabling those (next, build-gated) is the remaining cold
+lever. Bug #2 (App Builder) is verified structurally (node_modules .tar.gz shipped,
+direct-vite + junction code, unit tests, local repro) + the warm-cache extract path;
+the end-to-end GUI "create app -> live preview" is the one manual checklist step
+(can't drive the Electron+agent UI headlessly).
+
+## Net time decreased per step (measured)
| step | before | after | saved |
| --- | --- | --- | --- |
diff --git a/docs/perf/winv2/make_graphs.py b/docs/perf/winv2/make_graphs.py
index 353527a6..8c0a9c2f 100644
--- a/docs/perf/winv2/make_graphs.py
+++ b/docs/perf/winv2/make_graphs.py
@@ -173,6 +173,38 @@ def appbuilder_chart():
return "\n".join(out)
+def startup_beforeafter_chart():
+ """Before/after grouped bars for the signed-build startup result (Task #10)."""
+ path = os.path.join(HERE, "startup_beforeafter.csv")
+ if not os.path.exists(path):
+ return None
+ with open(path, newline="", encoding="utf-8") as f:
+ data = list(csv.DictReader(f))
+ w, h = 900, 320
+ pad_l, pad_r, pad_t, pad_b = 60, 30, 56, 90
+ plot_w, plot_h = w - pad_l - pad_r, h - pad_t - pad_b
+ vmax = max(max(int(r["before_ms"]), int(r["after_ms"])) for r in data)
+ n = len(data); group = plot_w / n; bw = group * 0.30
+ out = [f'')
+ return "\n".join(out)
+
+
def main():
data = rows()
open(os.path.join(HERE, "baseline_startup.svg"), "w", encoding="utf-8").write(bars_chart(data))
@@ -183,6 +215,10 @@ def main():
if ab:
open(os.path.join(HERE, "appbuilder_breakdown.svg"), "w", encoding="utf-8").write(ab)
wrote += " + appbuilder_breakdown.svg"
+ sba = startup_beforeafter_chart()
+ if sba:
+ open(os.path.join(HERE, "startup_beforeafter.svg"), "w", encoding="utf-8").write(sba)
+ wrote += " + startup_beforeafter.svg"
print("wrote " + wrote)
diff --git a/docs/perf/winv2/startup_beforeafter.csv b/docs/perf/winv2/startup_beforeafter.csv
new file mode 100644
index 00000000..4b45e85f
--- /dev/null
+++ b/docs/perf/winv2/startup_beforeafter.csv
@@ -0,0 +1,3 @@
+metric,before_ms,after_ms
+cold backend-ready (54-138s -> 22.5s),96000,22500
+warm backend-ready (9-10s -> 5.0s),9500,5000
diff --git a/docs/perf/winv2/startup_beforeafter.svg b/docs/perf/winv2/startup_beforeafter.svg
new file mode 100644
index 00000000..ea08784f
--- /dev/null
+++ b/docs/perf/winv2/startup_beforeafter.svg
@@ -0,0 +1,21 @@
+
\ No newline at end of file