Skip to content

chore(dcz): roll the shell and page dictionaries onto the deployed build - #178

Merged
oddharsh merged 1 commit into
mainfrom
chore/roll-shell-dictionaries
Jul 31, 2026
Merged

oddharsh merged 1 commit into
mainfrom
chore/roll-shell-dictionaries

Conversation

@oddharsh

Copy link
Copy Markdown
Owner

Claim card

Per UNDERSTANDING-REVIEW.md. Scale: build input, no runtime code changes.

Intended model. Adopt the currently-deployed shell and pages as dictionary candidates. Two releases shipped since the last roll on 2026-07-30 (#176 removed View Transitions, #175 moved the homepage grid fragment off the script discovery path), and between them they reminted luna.css, nav.js, notepad.js, lens.js, and tooltip.js. This is build input only: no worker code, no page markup, no runtime behavior.

Prediction. Nothing changes for a visitor today. Deltas already worked, because they diff the current asset against older candidates. What changes is the next release: a visitor who loads the site now holds bytes that will have a matching dictionary then, instead of falling back to the family corpus or plain brotli. Concretely, 7 of 9 shell assets went from "current hash absent from the set" to present, and 11 pages that had no snapshot at all (/bot, /photos, /restore, /updates, /lens, /pixel-peeper, 5 × /writing/*) now have one.

Likely misconception / first plausible failure. That a roll is safe to run from any green build. It is not, and this PR nearly demonstrated the failure. shell:roll reads the local .build/holding/a, so it will happily adopt whatever the local toolchain emits. My first build produced lens.0407f2b8 and tooltip.17839b04 against a production serving lens.eed21892 and tooltip.549f1cd0, with a clean source tree. The cause was oxc-minify 0.141.0 installed against a 0.142.0 lockfile: 0.142.0 constant-folds 1024*1024 into 1048576, two bytes, which remints the content hash. Committing that would have filed dictionaries for bytes no browser has ever held, defeating the entire point, and it would have looked like a clean successful roll. The second misconception is reading a stale /luna.css as evidence the deploy had not landed; that URL is the short-cached unhashed fallback, and the hashed /a/ URL is the honest one (CLAUDE.md gotcha 12).

Evidence.

  • Rolled from 4f09a42, which is where both origin/main and origin/production point, and whose Workers Build completed successfully at 04:35:56Z.
  • All six /a/ URLs referenced by the live pages (/, /garage/, /lwe/, /lens, /writing) reproduce byte-exact from this build after npm ci. That equality is the precondition, and it is what failed before.
  • Confirmed the deployed shell is genuinely post-perf(shell): drop View Transitions, so navigation lands as fast as it arrives #176: /a/luna.76d1dbd8.css contains 0 occurrences each of @view-transition, view-transition-name, and axp-window.
  • shell:roll is idempotent: a second run adopted 0 and pruned 0.
  • Rebuild emits 12 shell deltas and 80 page deltas, and correctly emits no self-delta for a freshly adopted candidate.
  • Gates: 67/67 contract tests, 78 routes / 0 hard failures, pages:check, perf-budget, check-wrangler.

Remaining uncertainty.

  • The benefit is unobservable until the next release. Every check here confirms the candidates are the right bytes; none of them can show the improved ratio, because that needs a future asset to diff against. The first real measurement is npm run dcz:check after the next deploy.
  • The build reproducibility gap is patched, not fixed. npm ci restored parity for this run, but nothing in the repo forces a roller to be on lockfile-exact dependencies, and the failure is silent. A guard in roll-shell-dictionary.mjs that verifies built hashes against live before adopting would close it. I did not add one here, to keep this PR to the chore itself.
  • Pruning uses mtime, which is a proxy for release order rather than a record of it. It was correct here, but a roll performed out of order could evict the wrong candidate.

What this is

The post-deploy half of #176. Routine dictionary maintenance, deliberately a human step because it writes into the source tree and build.mjs must never do that.

Adopted 6 shell candidates and 41 page snapshots; pruned 3 and 28 by the keep-3 / keep-2 rules.

Shell coverage after the roll, every asset now carrying its current hash:

asset candidates current in set
hoist 2 yes
lens 3 yes
lens-browser 3 yes
luna 3 yes
lwe-base 2 yes
nav 3 yes
notepad 2 yes
quiz 1 yes
tooltip 2 yes

quiz sitting at a single candidate from 2026-07-26 is correct rather than stale: quiz.js has not changed since, so its lone candidate is the current one. Age and staleness are different things here.

🤖 Generated with Claude Code

The last roll was 2026-07-30, and two releases have shipped since: #176 took
View Transitions out of the shell and #175 moved the homepage grid fragment off
the script discovery path. Between them they reminted luna.css, nav.js,
notepad.js, lens.js and tooltip.js, so seven of nine shell assets had no
dictionary matching the bytes a visitor now holds. Deltas kept working the
whole time (they diff against the OLDER candidates, which is the point), but
anyone who loaded the site since those deploys would have gone into the next
release with nothing to diff against.

Rolled from the deployed commit, 4f09a42, which is what origin/production
points at and what aadhar.sh actually serves. All six /a/ URLs referenced by
the live pages reproduce byte-exact from this build.

Adopted 6 shell candidates and 41 page snapshots; pruned 3 and 28 by the
keep-3 / keep-2 rules. Every shell asset now carries its current hash.

One trap worth recording, because it cost the first attempt: the local build
did NOT reproduce production for lens.js and tooltip.js, and the source tree
was clean. The cause was oxc-minify 0.141.0 installed against a 0.142.0
lockfile; 0.142.0 constant-folds `1024*1024` to `1048576`, a two-byte
difference that remints the content hash. Rolling on that build would have
committed dictionaries for bytes no browser has ever held, which is the exact
failure the "roll from the deployed build" rule exists to prevent, and it
would have looked like a successful roll. `npm ci` before a roll, and verify
the built /a/ hashes against the live ones before trusting the output.

Gates: 67/67 contract tests, 78 routes, page contracts, perf budget,
wrangler check. Build regenerates 12 shell deltas and 80 page deltas, and
correctly emits no self-delta for a freshly adopted candidate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Understanding-first review

This is a prompt, not a score or merge gate. Before approving, reconstruct the change:

  • My model: What do I think this PR does?
  • Mechanism: What path or invariant makes that behavior true?
  • Falsifier: What would prove my model wrong?
  • Evidence: What test, trace, dry-run, or live check did I inspect?
  • Residual uncertainty: What remains unproven?

For docs and dependency changes, a compact answer is enough. For behavior or production changes, include the causal path and one concrete counterexample. The author’s claim card belongs in the PR description; this prompt is for the reviewer’s independent reconstruction.

See UNDERSTANDING-REVIEW.md.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
aadhar-sh 27ccce6 Jul 31 2026, 04:47 AM

@oddharsh
oddharsh merged commit 331c1a6 into main Jul 31, 2026
4 checks passed
@oddharsh
oddharsh deleted the chore/roll-shell-dictionaries branch July 31, 2026 05:00
oddharsh added a commit that referenced this pull request Jul 31, 2026
Six releases landed after the last roll (#179 through #184), and between them
they reminted nav.js, luna.css and tooltip.js. Those three were live with no
matching candidate, so anyone loading the site right now would have gone into
the next release with nothing to diff against.

Adopted 3 shell candidates and 40 page snapshots; pruned 2 and 36 by the
keep-3 / keep-2 rules. All nine shell assets carry their current hash again.

This roll also makes the PREVIOUS one measurable, which is the thing #178's
claim card listed as unprovable at the time. Its candidates are what the new
shell diffs against, and they are carrying almost the whole file:

  nav.js      vs 1df49c71    226 B  against 13341 B plain brotli   98.3% off
  luna.css    vs 76d1dbd8     75 B  against  8479 B plain brotli   99.1% off
  tooltip.js  vs 549f1cd0    351 B  against  2651 B plain brotli   86.8% off

A visitor who loaded the site during the 4f09a42 window now pays 226 bytes for
a nav.js that would otherwise cost 13 KB. That is the whole point of the
committed dictionary set, and it only becomes observable one release later.

Rolled from 984804e, which is where origin/main and origin/production both
point. `npm ci` first, then verified all six /a/ URLs referenced by the live
pages reproduce byte-exact before adopting anything — the oxc-minify version
skew that nearly poisoned #178 is still an unguarded footgun, so the check
stays manual until roll-shell-dictionary.mjs does it itself.

Gates: 74/74 contract tests, 78 routes, page contracts, perf budget.

Co-authored-by: Aadharsh Pannirselvam <19518661+oddharsh@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
oddharsh added a commit that referenced this pull request Aug 12, 2026
…stops rotting (#348)

The dictionary sets are the one build input no build step may produce. A returning
visitor holds whatever `/a/` asset and page production served them last, offers its
hash, and earns a delta only if those exact bytes are committed here. build.mjs
cannot derive that from source, so the sets are committed, and committing them is a
step someone has to remember.

Nobody remembered since #178 on 2026-07-30, 161 commits back.

## What was broken

Every dictionary-carrying shell asset production serves was missing from
`www/a-dict`: nav.1c6af07b, luna.1523f4f6, hoist.d9081560, tooltip.5dad2389,
lens.88fba28b. Main built its six shell deltas against #178's candidates, which no
living browser holds, so a returning visitor took plain brotli on the render-blocking
path. After the roll, against the bytes browsers actually hold:

| | stale | rolled |
|---|--:|--:|
| `luna` delta | 7,781 B (no match, plain br) | **155 B** |
| `nav` delta | 5,973 B (no match, plain br) | **1,113 B** |

Per-page deltas went 121,288 B over 92 to 74,337 B over 94, because the snapshots
now match live pages rather than pages from twelve days ago.

## Why dcz:check said PASS the whole time

Its shell probe picks an a-dict candidate that is NOT the live hash, offers it, and
asserts dcz comes back. build.mjs builds a delta for every a-dict entry, so that is
true by construction; the probe reads the live hash only to exclude it. It is the
same self-agreeing shape as the Markdown-twin test in gotcha 24 and the page tier's
old snapshot probe in gotcha 20.

`live shell is covered by a-dict` asks the question a returning visitor asks: are the
bytes THEY hold covered? Bases with no a-dict history are skipped, since a new asset
cannot have a dictionary until it has been served once. Byte-compared, not
filename-compared, because 8 hex of a sha256 is not the digest the browser keys on.
Advisory on purpose: like infra:check's edge tier it reads production, so making it
required would deadlock the release that would clear it.

## Why it can now run unattended

`shell:roll` adopted from `.build/www/a`, which forced it to run from the deployed
commit. `--live` (`pnpm run dict:roll`) reads production instead, the same way the
page half has since gotcha 20. Adopting from a build can only capture THIS commit's
shell, so a release that goes by without a roll is unrecoverable, and on an unramped
tree it adopts bytes nobody holds while evicting one still in use (KEEP is 3).
Whatever is live is protected from the prune. It refuses outright if production reads
empty, since an empty read hands the prune an empty `current` set, which is exactly
when it is free to evict what browsers are holding.

`.github/workflows/dictionary-roll.yml` runs it nightly and opens a PR when anything
moved. It holds no Cloudflare credential: `--live` is plain HTTPS and the PR goes out
on GITHUB_TOKEN. It cannot merge, because `main` takes zero bypass actors and that
property is the whole release model.

## CLAUDE.md

Two paragraphs described a `.github/workflows/ramp.yml` running the ramp from a
scoped environment secret. Verified 2026-08-12: no such workflow in any commit, the
environments are `copilot` and `production` rather than the canary/full pair, and the
only repo secret is the six-read-scope CLOUDFLARE_API_TOKEN. What landed on
2026-08-06 was the release-guard rewrite alone. Both blocks are relabelled as a PLAN
rather than deleted, since the reasoning about where a secret lives is still right.

Also corrected: "not urgent, a dictionary 11 days stale still gave 87-93%" is true of
a STALE dictionary and false of an ABSENT one. The page tier degrades to the family
corpus at ~26%; the shell tier has no fallback.

## Drive-by

Four workflows bootstrapped pnpm@11.20.0 while package.json has pinned 11.21.0 since
#310 two days ago, each under a comment calling package.json the source of truth.
Behaviour-neutral (self-switching is on by default, so 11.21.0 already ran every
time), but the gap stops being harmless the moment it crosses a major: pnpm 12 ships
a native CLI that an older pnpm cannot exec, which is gotcha 30. A contract test now
pins every workflow's bootstrap to package.json, and counts what it checked so a
regex that stops matching cannot pass silently.

Co-authored-by: Aadharsh Pannirselvam <19518661+oddharsh@users.noreply.github.com>
This was referenced Oct 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant