feat(retention): retain content-hashed JS/CSS bundles across deploys (Workstream A) - #1658
Merged
Conversation
…, add network fail-open tests
…ocal deploy path)
…SS (.hash.css pattern)
…t before on static
Adds test/smoke/asset-retention.smoke.test.js — post-deploy observability check that every bundle listed in the live /_retained-assets.json responds 200. Self-skips when SMOKE_BASE_URL is unset (local / unit runs), exactly like browse.smoke.test.js and other smoke-tier siblings. Uses fetchWithRetry for consistent retry behavior on transient 502/503s.
…ention smoke
- Pass { redirect: 'follow' } to all three fetchWithRetry calls (manifest
GET ×2, bundle HEAD) so an unexpected approuter rewrite doesn't surface
as a false 3xx failure. fetchWithRetry defaults to redirect:'manual' per
smoke.config.js contract; override is explicit here.
- Extract parseManifest() helper that reads the body as text first, then
JSON.parse()s it inside a try/catch — on a 200 non-JSON body it calls
expect.fail() with the first 120 chars so the failure is diagnostic
rather than an uncaught SyntaxError.
Adds 'Asset-hash retention (#1604 follow-up)' subsection to docs/developers/architecture/build.md describing what hugo/static/_retained-assets.json is, the 48-hour retention window, fail-open carry-forward from the live approuter, and why unioning content-hashed bundles is safe.
…section
The retain-asset-bundles.cjs script operates on hugo/public/{js,css} and
writes hugo/public/_retained-assets.json (after build:hugo; the approuter
builder copies hugo/public → approuter/static so the manifest is served at
/_retained-assets.json). The original subsection incorrectly said hugo/static.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Asset-hash retention (Workstream A of the 2026-08-11 durable-fix spec)
Implements the ship-first workstream from the merged design (#1645): retain prior content-hashed JS/CSS bundles across deploys so edge-cached HTML never 404s on a bundle hash a newer deploy deleted. This closes the stale-HTML → deleted-hash class — including the live JS-island staleness (
validation.js,navigator.js, …) surfaced during the 2026-08-10/11 incident.Mechanism
scripts/lib/asset-retention.cjs— puremergeRetention({currentFiles, retainedManifest, nowMs, windowMs}): unions the current build's hashed bundles with in-window prior ones; prunes expired; never drops a hash still in the current build.scripts/retain-asset-bundles.cjs— runs afterbuild:hugoonhugo/public/{js,css}; fetches the live approuter's served/_retained-assets.json, downloads in-window carried bundles, re-emitshugo/public/_retained-assets.json. Fail-open on every network path.<name>-<base62≥8>.{js,css}(dash) and Hugo-fingerprinted<name>.<hex≥32>.css(dot); ignores committed unhashed files.build:all(local, after Hugo) and rootmta.yamlbefore-all (CI, after the Hugo build);APPROUTER_URLexposed to the CImbt buildstep.RETENTION_WINDOW_HOURS, overridable). Content-hashed filenames are immutable, so unioning is always safe.Tests / verification
test/unit/asset-retention.test.js(4) +test/unit/retain-asset-bundles.test.js(7) — 11/11 pass. Fullnpm testgreen (only pre-existing load-sensitivecheck-srv-qaflakiness, unrelated).test/smoke/asset-retention.smoke.test.jsasserts every bundle in/_retained-assets.jsonserves 200; self-skips withoutSMOKE_BASE_URL.Notes
export APPROUTER_URLfor carry-forward (CI sets it per-env); fail-open otherwise. Documented inbuild.md./admin/rebuild+deploy-self-heal) is a separate follow-up plan.Plan:
docs/superpowers/plans/2026-08-11-asset-hash-retention.md. Squash-merge recommended (history includes intermediate fix commits).