Files
ytplayer/plans/phase6-investigation.md

138 lines
8.5 KiB
Markdown

# Phase 6 investigation
All comparisons use the current instrumented harness with matching frontend and
server from detached worktrees beneath `perf/.tmp`. No production requests.
Command template (three fresh contexts):
```
node perf/baseline.mjs --runs 3 --browser webkit --profile lte --scenario cold --source-root perf/.tmp/phase6-<variant> --out perf/results/phase6-<variant>.json
```
| Source | Commit | LTE FCP median ms | Wire bytes | app.js positive responses/sample |
|---|---|---:|---:|---:|
| Phase 2 | 4950d3e | 1044 | 1191063 | 2 |
| Immediately before lazy layouts | d98d444 | 1032 | 1191686 | 2 |
| First lazy-layout cut | 5d6a154 | 1615 | 1106796 | 2 |
| Phase 3 tip | cd4a25a | 1646 | 1108191 | 2 |
| Phase 5 tip (same product as Phase 4) | 21c1273 | 1736 | 1097007 | 2 |
| Phase 5 + worker force-cache | scratch | 1745 | 1097006 | 2 |
| Phase 5 + parser-owned classic CSS | scratch | 898 | 1097051 | 2 |
| Phase 5 + classic preload | scratch | 909 | 1097032 | 2 |
The adjacent-commit measurements isolate `5d6a154`, not the Phase 4 extraction.
All three FCP samples, complete proxy waterfalls, and page Resource Timing are
stored in each result. The first sample often includes startup compression cost;
it is retained in every median, not discarded.
Phase 3 sample 2: common styles finish around 880 ms. The parser-written classic
stylesheet is discovered at 416 ms but reaches the proxy only at 1434 ms and
finishes at 1607 ms. FCP is 1646 ms. Phase 5 sample 2: classic reaches the proxy
at 1540 ms, finishes at 1711 ms, FCP is 1736 ms. Speculatively discovered body
scripts already occupy the request queue when the selected stylesheet arrives.
Phase 2 discovers its declarative styles together around 231 ms, FCP 1044 ms.
The first service-worker request is at 2458 ms in Phase 3 and 2337 ms in Phase 5,
AFTER paint. Worker install competition does not explain this FCP regression.
Font preload URLs match the font CSS; late layout CSS is the final rendering
barrier. Making classic CSS declarative again removes the delay without changing
CSS bytes or cascade order. This scratch variant unconditionally applies classic
and is diagnostic only; the shipping fix must retain selected-layout behavior.
The modern synchronizer already uses default fetch caching. Changing it to
force-cache has no effect: every WebKit sample still transfers app.js twice.
The page and worker cannot reuse these HTTP-cache responses in this harness.
The next hypothesis was a same-build page handoff reusing the page HTTP cache.
The prototype checked both the X-Asset-Hash header and decoded-body SHA-256, and
stripped compressed transport headers before constructing the cached Response.
Updates and legacy migration must continue through their existing worker path.
The shipping CSS fix is a preload, not an unconditional stylesheet. Its three-run
FCP is 909 ms; the first classic request reaches the proxy at 250 ms and completes
at 423 ms. Lazy still applies only the saved layout, in the original cascade order.
Other selected layouts may speculatively download the small classic stylesheet;
it never applies or blocks their rendering. No inline script or CSP change.
The page-to-worker prototype successfully delivered bodies, but still transferred
app.js twice. `phase6-handoff-mode.json` records the two requests: identical URL,
`Accept-Encoding: gzip, deflate`, no-cors mode and immutable response, but script
versus empty request destinations. Force-cache in the page did not reuse the
script body's cache either. This prototype was discarded, not shipped.
A fetch preload plus a later fetch also failed the one-body assertion. The
shipping fix instead starts one cache-first fetch promise in the head loader.
The final eager app-bootstrap consumes that response, verifies header AND SHA-256,
and caches it before executing unchanged app.js as a classic script. A specific
server-generated CSP hash authorizes only those exact bytes; unsafe-inline remains
absent. DOMContentLoaded boot runs exactly once; the temporary script node and
response promise are released. Native, insecure HTTP and rollback modes retain
external classic execution. Only server asset-manifest rendering changed; no
frontend imports, worker sync algorithm, update flow or playback source changed.
The initial owned-response trial passed with one app.js body, FCP 1040 ms and
981468 wire bytes (one fresh sample; not a final median). Five-run acceptance and
regression results will be recorded separately. Historical diagnostics retained
all samples. Full-harness configurations now reset to the base tree before each
browser/profile, preventing a preceding synthetic update from contaminating the
next cold build. The historical invalid media fixture remains unchanged: home
launch only, not autoplay/audio latency.
Self-review added explicit N-1 app boot after eviction: verify the retained body
and use its exact classic URL through the controlling worker. It never fills the
new key with old bytes. Stale core execution additionally requires every group
contract to match the running shell's expectations; a core or feature contract
change rejects it. Both-engine lazy smoke now changes app bytes, evicts the new
entry, boots N-1 offline, then repairs the current entry before the existing
feature pinning/contract/playback checks. No source in app.js changed.
The all-engine inline prototype's completed five-run Chromium/LTE profile had
cold boot 6748 ms, warm boot 458 ms (Phase 5: 410 ms), offline boot 204 ms, core
update 870 ms. This was a real warm regression, so the full sweep was interrupted
before accepting its results. Raw output: perf/.tmp/phase6-inline-chromium-profile.log.
The native-script controlled variant measured warm 414 ms over five samples.
Shipping capture is therefore restricted to WebKit (including iOS CriOS); other
engines preload and retain their native classic-script/code-cache path. Normal
native cache hits avoid decoding/rehashing; retained core fallback still enforces
all group contracts. The new worker marks stale responses no-store and strips
decoded transport headers, preventing browser resource-cache poisoning under a
new URL. CacheStorage's exact keys and previous entries stay unchanged.
The native-window contract lookup trial regressed Chromium warm boot (~698 ms).
It is rejected. The worker now owns the all-group stale app contract guard
(pure planner and actual worker VM tests first); native boot has no window-side
CacheStorage or hashing work. WebKit's byte-verified owned response path is
unchanged. The native fixture models the actual worker fallback decision instead
of a second page-side decision. All 221 frontend tests and 184 server tests pass;
both-engine lazy/offline/eviction/pinning/contracts/playback smoke passes.
The WebKit warm prototype also paid repeated handoff/cache/hash work (warm
~98 ms versus Phase 5 83 ms). Capture now applies only to uncontrolled WebKit
visits. Controlled warm/offline/update pages use the worker's native exact-URL
script path, including its strengthened contract guard. A failing head-loader
test precedes this change; all 222 frontend and 184 server tests and both-engine
lazy smoke pass. Cold capture and both rollback flags are unchanged.
The final native-path review caught a new Chromium paint regression: five-run
FCP 1616/2328 ms (LTE/lossy), despite faster boot. A contemporary Phase 5 LTE
control measured 1360 ms over three runs. Lowering the new app preload priority
reduced FCP to 1396 ms (three runs). Native parser execution restores app's
original DOMContentLoaded boot boundary. A rejected no-preload controlled-page
trial worsened WebKit warm boot to 98 ms; native cache preloading remains.
The standalone helper adds a CacheStorage script read to native startup. Packing
the loader into the existing final core script, section-rail.js, measured Chrome
warm 425 ms versus contemporary Phase 5 426 ms (five runs); WebKit measured
95 ms versus 88 ms, so zero warm overhead is not yet established. The merged
three-run Chrome LTE cold trial measured FCP 1384 ms and boot 6512 ms. Core's
contract becomes 2 because an old section script lacks this loader and cannot
safely substitute for it. No Phase 5 SHELL file is removed; the experimental
Phase 6 standalone helper is removed. Native-order, exact final-script packing
and contract tests precede implementation. All 223 frontend and 184 server tests
and both-engine lazy/offline/eviction/pinning/contracts/playback smoke pass.
The intermediate native-auto-priority full dataset is retained as a rejected
trial; it is not the final acceptance result. Source directories named in trial
JSONs are dirty diagnostic worktrees of 9408ee0 with exactly the changes above.