Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/hosting.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@ Automatic [static publication](release-publication.md) is enabled through the `p

The authenticated provider mapping, root identities, handler policy and preserved overlays were independently verified during installation and the canonical pilot. Routine publication checks those pinned target bindings together with the complete source payload, origin/edge responses, TLS, cache behavior and zone/DNS configuration. Provider layout or handler-policy changes require operator verification and an updated binding before routine publication resumes.

The authored canonical redirect must run before any provider rule that changes
encoded paths. Preserve the complete certificate-manager block and static handler
guard, and independently verify that the installed manager preserves the reviewed
order. Keep certificate renewal active. Moving an overlay requires a separate
reviewed operator transition; ordinary publication cannot change its policy or
adopt a different layout. Preserve the original completed attempt and its full
rollback evidence outside routine retention, pause publication with the queue
idle, and bind the exact before/after bytes under the existing target lock. The
transition must remain recoverable after process exit, preserve target identities
and advance its generation. Never reset the journal or loosen redirect acceptance
to recover a failed publication.

The remaining infrastructure stages are specified in [release automation](release-automation.md). Worker and database promotion have separate credentials, compatibility and recovery requirements; the static workflow cannot update them.

## Available platform capabilities
Expand Down
8 changes: 8 additions & 0 deletions docs/release-publication.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ query bytes must survive every hop from HTTP or `www` to HTTPS apex. The origin
rule reads the original `THE_REQUEST` because Apache-compatible `REQUEST_URI`
is already decoded; see the [rewrite variable documentation](https://httpd.apache.org/docs/2.4/mod/mod_rewrite.html#rewritecond).

Inspect the complete installed rule order, including provider-managed redirects.
A provider HTTP-to-HTTPS rule can terminate a request before the authored
canonical rule runs. Correct HTTPS and edge responses do not prove that this
first origin hop preserves the URL. Provider probes must cover HTTP and HTTPS,
apex and `www`, GET and HEAD, and each raw `Location` along the chain; an isolated
HTTPS-only subdirectory probe is insufficient. Do not add forwarding headers
that bypass provider rules to a publication acceptance request.

Before a transition, the client reads only `site/.htaccess` from the exact
predecessor commit and validates it against the independently bound live
manifest. It never executes historical code or substitutes the new server block
Expand Down
30 changes: 30 additions & 0 deletions scripts/test-static-publication.py
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,36 @@ def get(self, path, *args, **kwargs):
with self.subTest(mutation=number), self.assertRaisesRegex(ArtifactError, 'redirect_mismatch'):
client.redirects()

def test_origin_http_provider_hop_is_checked_even_when_edge_and_https_are_correct(self):
class Client(http.HTTP):
def get(self, path, surface, host, scheme, retry=False, method='GET'):
target = http.WWW if scheme == 'http' and host == http.WWW else http.HOST
route, separator, query = path.partition('?')
if (surface, host, scheme, method) == ('origin', self.bad_host, 'http', self.bad_method):
if self.encoded in route:
self.bad_calls.append((host, method, route))
route = route.replace(self.encoded, self.replacement)
return {'status': 301, 'headers': {'location': 'https://' + target + route + separator + query}}

# Observed first-hop defects: a later canonical rule cannot repair them.
cases = [('%23', '#'), ('%3F', '%3f'), ('%2F', '/'), ('%2f', '/'),
('%C3%A4', '\u00c3\u00a4')]
for host in (http.HOST, http.WWW):
for method in ('GET', 'HEAD'):
for encoded, replacement in cases:
with self.subTest(host=host, method=method, encoded=encoded):
client = Client('1.1.1.1')
client.bad_host, client.bad_method = host, method
client.encoded, client.replacement = encoded, replacement
client.bad_calls = []
path = '/oss-redirect-check/a' + encoded + 'b' + http.REDIRECT_QUERY
for surface in ('origin', 'edge'):
response = client.get(path, surface, http.WWW, 'https', method=method)
self.assertEqual(response['headers']['location'], 'https://' + http.HOST + path)
with self.assertRaisesRegex(ArtifactError, 'redirect_mismatch'):
client.redirects()
self.assertEqual(client.bad_calls, [(host, method, path.partition('?')[0])])

def test_public_api_rejects_cached_or_unpublished_results(self):
state, published = ['DYNAMIC'], ['published']
class Client(http.HTTP):
Expand Down