Skip to content

feat(docs): let a renamed site redirect its retired hostnames - #635

Merged
rubenvdlinde merged 2 commits into
mainfrom
feat/docs-canonical-host
Aug 29, 2026
Merged

feat(docs): let a renamed site redirect its retired hostnames#635
rubenvdlinde merged 2 commits into
mainfrom
feat/docs-canonical-host

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

The gap

docs-hosts makes 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.

openconnector.conduction.nl  200  <no redirect — serves content directly>
docudesk.conduction.nl       200  <no redirect — serves content directly>
openbuild.conduction.nl      200  <no redirect — serves content directly>
scholiq.conduction.nl        200  <no redirect — serves content directly>
planix.conduction.nl         200  <no redirect — serves content directly>

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 optional canonical-host input adds a small script in front of the asset binding:

  • hostname ≠ canonical → 301 to the same path and query on the canonical host
  • otherwise → env.ASSETS.fetch(request), untouched

Delegating rather than reimplementing is deliberate: the asset binding keeps its own html_handling and 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-host unset the generated wrangler.toml is 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:

without canonical-host with it
wrangler.toml identical to today gains main + binding = "ASSETS"
worker script not emitted emitted, passes node --check

And the handler was exercised against real Request objects:

ok   openbuild.conduction.nl/docs/intro      -> 301 buildiq.conduction.nl/docs/intro
ok   openbuild.conduction.nl/docs/a/b?x=1&y=2 -> 301 buildiq.conduction.nl/docs/a/b?x=1&y=2
ok   openbuild.conduction.nl/                -> 301 buildiq.conduction.nl/
ok   buildiq.conduction.nl/docs/intro        -> 200 (serves asset)
ok   buildiq.conduction.nl/                  -> 200 (serves asset)

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, with openbuild as its retired host), verified live, before the remaining twelve.

Conduction Release Bot 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.
@rubenvdlinde
rubenvdlinde merged commit a83b533 into main Aug 29, 2026
39 checks passed
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
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