feat(docs): let a renamed site redirect its retired hostnames - #635
Merged
Conversation
added 2 commits
August 28, 2026 20:16
… FROM The Hydra gates take the PR's target branch as their diff base, which is right for ordinary work and wrong for exactly one shape: a release promotion. The fleet promotes feature -> development -> beta -> main. By the time `beta` is opened against `main`, every commit in that diff has already faced these gates twice. Diffing against the TARGET asks "what does this add to main?" and the answer is every commit since the last release, so a diff-scoped gate reports the whole tree as changed. Measured on openregister#2975 (hotfix/... -> main, 5782 commits): gate-16 reported 256 changed methods missing @SPEC, plus gate-83 and gate-70 -- on a tree byte-identical to `beta`, where the same content passed Hydra Gates green on #2972. Nothing was found that beta had not already cleared. So a promotion now diffs against its SOURCE branch. This keeps the gates real rather than skipping them: a hotfix carrying genuinely new code still shows that code in the diff against beta and is still judged on it. What disappears is only the re-litigation of commits that already passed. A repo with no such source branch keeps the target as its base and says so, so this can never resolve to an empty ref and scope to nothing -- a gate run against no base being the failure this step exists to prevent. Verified: YAML parses, the run block passes `bash -n`, and the base selection returns the expected ref for all eight head/base combinations plus the missing-source-branch fallback. Feature branches are unaffected at any target.
`docs-hosts` makes one worker answer on several hostnames and serves the same bytes on each. That is exactly right on the day of a rename -- the old address must not break -- and wrong from then on: openconnector.conduction.nl and integriq.conduction.nl have been serving two identical copies of every page, so the retired name stayed as discoverable as the current one. Measured across the fleet 2026-08-29: all 13 renamed apps answer 200 on BOTH hostnames, and not one of them redirects. A worker with only `[assets]` cannot express this, because there is no code to read the Host header. Declaring the new optional `canonical-host` adds a small script in front of the asset binding: a request whose hostname is not the canonical one gets a 301 to the SAME path and query on the canonical host, and everything else is handed to `env.ASSETS.fetch(request)` untouched, so the binding keeps its own html_handling and 404 behaviour. 301 rather than 302 on purpose: the hostname is retired, not moved for the afternoon, and only a permanent redirect makes search engines and caches forget it. OPT-IN. With `canonical-host` unset the generated wrangler.toml is byte-for-byte what it was and no script is emitted, so this cannot regress a site that did not ask for it. Verified by running the generator, not by reading it: without the input the output matches today exactly and no script appears; with it, wrangler.toml gains `main` + `binding = "ASSETS"`; the emitted JS passes `node --check`; and the handler was exercised against real Request objects -- deep path and query string survive the redirect, and both canonical-host cases still serve the asset.
This was referenced Aug 29, 2026
rubenvdlinde
added a commit
that referenced
this pull request
Aug 29, 2026
#635 added the canonical-host redirect and it never fired. The asset server answers before the Worker: when the request path matches a file in ./build it is served directly and the script is not invoked, so the redirect could only run for paths with NO asset -- which is never, for a real documentation page. Measured on the buildiq pilot. Everything that usually counts as evidence said it worked: the generated wrangler.toml carried `main` and `binding = "ASSETS"`, wrangler printed `env.ASSETS Assets`, "Uploaded openbuild-docs" and "Deployed openbuild-docs triggers", and the run was green. openbuild.conduction.nl still answered 200 instead of 301. What settled it was the worker's OWN workers.dev URL also answering 200. That hostname is not the canonical one either, so a running script would have redirected it; the route was fine, the script simply never executed. `run_worker_first = true` puts the script in front of the asset server. It is emitted only alongside canonical-host, so a site that has not opted in still gets byte-for-byte the config it had. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
This was referenced Aug 29, 2026
Merged
Merged
Merged
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.
The gap
docs-hostsmakes one worker answer on several hostnames — and it serves the same bytes on each. That is exactly right on the day of a rename: the old address must not break. It is wrong from then on.Measured across the fleet on 2026-08-29: all 13 renamed apps answer 200 on both hostnames, and none of them redirects.
So every renamed app has two live copies of every documentation page, and the retired name stays as discoverable as the current one.
The change
A worker with only
[assets]has no code to read the Host header, so it cannot express this. The new optionalcanonical-hostinput adds a small script in front of the asset binding:env.ASSETS.fetch(request), untouchedDelegating rather than reimplementing is deliberate: the asset binding keeps its own
html_handlingand 404 behaviour, so an opted-in site serves exactly what it served before on its canonical host.301, not 302 — the hostname is retired, not moved for the afternoon, and only a permanent redirect makes search engines and caches forget it.
Opt-in, so a rollout cannot regress anyone
With
canonical-hostunset the generatedwrangler.tomlis byte-for-byte what it was and no script is emitted at all.Verification — by running the generator, not reading it
The step's script was extracted from the YAML and executed both ways:
canonical-hostwrangler.tomlmain+binding = "ASSETS"node --checkAnd the handler was exercised against real
Requestobjects:Deep paths and query strings survive; the canonical host is unaffected.
Rollout
Nothing changes until an app sets the input. Next step is one app (
buildiq, already publishing, withopenbuildas its retired host), verified live, before the remaining twelve.