Skip to content

Phase 2: the single-host cutover, prepared (do not merge before Access steps A to E) - #112

Merged
ryandmonk merged 3 commits into
mainfrom
claude/composer-single-host-cutover
Oct 18, 2026
Merged

ryandmonk merged 3 commits into
mainfrom
claude/composer-single-host-cutover

Conversation

@ryandmonk

@ryandmonk ryandmonk commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

What this is

The single-host cutover change, prepared for review. Under the owner's single-host amendment, the experimental artifact deploys to the existing composer Worker, dspack-studio-composer, on composer.aesthetic-function.com, behind Cloudflare Access (docs/blocks/BETA-DEPLOY-PLAN.md). No new hostname, subdomain or Worker is created or named.

Do not merge before steps A to E of the cutover order are done and recorded:

  • Access configured with one-time PIN for the owner's exact email;
  • anonymous GET and POST denied;
  • workers.dev, Preview URLs and stray routes ruled out;
  • the owner signed in.

The owner merges it at step F.1. This PR deploys nothing by itself. Its merge redeploys only the block-free default artifact, through Workers Builds, with the hardened config. The release candidate's application code is unchanged, so that deployment is the rollback anchor.

What changes

  • wrangler.jsonc (Workers Builds, deploy-composer). It still serves the block-free apps/composer/out on the same Worker and route, and gains:
    • workers_dev: false, preview_urls: false and keep_vars: true, and previews_enabled: false on its one custom-domain route, so every deploy turns the custom domain's own Preview URLs off;
    • no vars: HOSTED_AI moves to the dashboard. Absent means on, and the value already set there is kept.
  • wrangler.composer-beta.jsonc → wrangler.composer-experimental.jsonc. The same Worker (dspack-studio-composer), route (composer.aesthetic-function.com), runtime, AI binding and rate-limit namespace (1001) as wrangler.jsonc, serving apps/composer/out-experimental. The two configs differ only in the assets directory.
  • deploy-composer-beta.yml → deploy-composer-experimental.yml.
    • Dispatch-only, from main, through the protected composer-experimental environment.
    • Its own token, CLOUDFLARE_API_TOKEN_EXPERIMENTAL, never the repository's production CLOUDFLARE_API_TOKEN. A dispatch to an environment that does not exist yet would create it unprotected, so the run then refuses for want of the secret instead of reaching the production token.
    • Before any deploy, with no exception, it checks that anonymous GET requests (page, /api/models, static assets) and an anonymous POST to /api/propose all go to the Cloudflare Access login.
    • It records the rollback anchor: the one version the latest deployment serves at 100%, read from wrangler deployments status --json. It refuses otherwise, a gradual split included. There is no first_deploy.
    • It records the live version and checks Access again whenever the deploy step ran, even when that step failed: wrangler makes the new version live before it publishes the custom domain's settings.
  • check-beta-deploy.mjs → check-experimental-deploy.mjs.
    • Both configs must be the one composer Worker with one route, the custom domain with previews_enabled: false.
    • A __proto__ key anywhere in either config is refused. The parser would turn it into a prototype that every key check misses and wrangler still reads.
    • workers.dev and Preview URLs must be off, keep_vars on, and neither config may declare vars or env.
    • The two configs must be equal apart from the artifact; the comparison ignores key order.
    • The Access check adds the POST probe, and refuses any failure to reach the hostname: it always exists.
    • The --access and --active-version modes load nothing from node_modules.
  • Tests.
    • beta-deploy.test.mjs → experimental-deploy.test.mjs, written fail-first: 59 of its assertions failed against the old guard, configs and workflow.
    • The review round added tests fail-first: 8 failing first. They catch each workflow regression the review showed passing: if: always(), || true, a workflow-level token, an extra --var or --route, a softened step condition, and a dropped persist-credentials.
    • operator-log.test.mjs: neither config declares vars.
  • Docs.
    • docs/deployment.md gets "The invite-only beta" runbook, replacing the superseded beta section. It covers the switches, the rollback, which restores the target version's variables, the stop conditions, and what now meets the Access login.
    • docs/BLOCKS.md layer 2, BETA-DEPLOY-PLAN.md (step C's command, the cutover-change section), BETA-OPERATIONS.md, the decision record, the plan's status note and backlog rows O2, O10 and O12 are updated to match.
    • O14 and O15 are added to the backlog.

Nothing in the application, the Worker's code, the block code or the request builders changes.

Safeguards

  • No anonymous exposure. The experimental artifact never reaches Cloudflare except through the Access-gated workflow. Workers Builds only ever builds apps/composer/out. Its branch builds run wrangler versions upload, which only reads the Worker's subdomain settings and never changes them (wrangler 4.146.0).
  • Rollback.
    • The anchor is recorded before every deploy, and the workflow refuses to deploy without one.
    • A merge to main deploys the block-free default artifact, an automatic rollback, until the workflow is dispatched again.
    • A rollback restores the target version's variables, so the runbook sets HOSTED_AI, BLOCKS_BETA and BETA_OPERATOR_LOG again after every rollback.
    • The beta ends by a forward change, never by reverting this one.
  • No new paid path. The POST probe's empty body would reach no model even if the Worker answered it. No step makes a model call. The canary stays blocked until the owner authorizes a temporary machine credential.

Review

One independent adversarial review of 3164245 found:

  • 0 material findings;

  • 7 minor findings. 721242c fixes six:

    • the anchor read as JSON;
    • the record and Access check after a partial deploy;
    • the __proto__ guard;
    • previews_enabled: false;
    • workflow tests that catch the regressions it found;
    • stale references in the release review.

    The seventh, splitting the workflow into a build job and a deploy job so the token boundary is real, is backlog O14. Workers Builds' own build and deploy share one container the same way.

The same reviewer then verified 721242c:

  • Resolved: five of the seven findings. The sixth is accurately deferred as O14.
  • NEW-1, material but conditional: Workers Builds' own wrangler must accept previews_enabled on the route, since an unknown route key is a hard error. Cleared: the Workers Builds branch build of 721242c, which reads this same wrangler.jsonc, succeeded. Pinning that wrangler is recorded as backlog O15.
  • NEW-2, minor: two test gaps, a second deploy step with if: failure() and a flow-style env:. 9908313 closes both: exactly one step may deploy, only the two post-deploy steps may carry a condition after the guard, and any workflow-level env fails.

Checks

  • Fail-first: 59 failing, then 123 of 123 in experimental-deploy.test.mjs and operator-log.test.mjs. The review round's tests: 8 failing, then 136 of 136; now 137 of 137. Each of 9 workflow mutations fails a test: the review's seven and the verification's two.

  • Locally, pnpm typecheck exited 0, and pnpm test passed in every workspace:

    • composer 510;
    • composer-core 445;
    • block-bench 268;
    • agent 50;
    • shadcn-renderers 25;
    • replay 21;
    • wireframe-renderers 11;
    • agui-bridge 6;
    • web 6;
    • scenarios 5;
    • a2ui-ingest 3;
    • contracts 2.

    The composer suite was re-run on the final tree after rebasing onto the merged docs (510 of 510), and after the review's fixes (523 of 523, typecheck exit 0, then 524 of 524).

  • wrangler 4.146.0 and 4.151.1 read both configs offline with no warning, with previews_enabled: false on the route and empty vars. Workers Builds' own branch builds of 3164245 and of 721242c, which has the route key, succeeded.

  • Every relative link resolves. No new em-dash: the one in an added line of wrangler.jsonc is the existing comment's, re-wrapped.

🤖 Generated with Claude Code

https://claude.ai/code/session_01R6Ax8qvfN3gYUC85oSGnoi

…e composer Worker, behind Access)

Under the owner's single-host amendment, the invite-only beta is the existing
Composer: composer.aesthetic-function.com and its Worker,
dspack-studio-composer, behind Cloudflare Access. This change retargets the
experimental deploy path at that Worker and hardens the default config. It
deploys nothing by itself: its merge redeploys only the block-free default
artifact through Workers Builds, and the owner merges it at step F.1 of the
cutover order, after Access is configured and verified (A to E).

- wrangler.jsonc: workers_dev false, preview_urls false, keep_vars true, and
  no vars; HOSTED_AI moves to the dashboard (absent means on).
- wrangler.composer-beta.jsonc -> wrangler.composer-experimental.jsonc: the
  same Worker, route, runtime, bindings and rate-limit namespace as
  wrangler.jsonc, serving apps/composer/out-experimental.
- deploy-composer-beta.yml -> deploy-composer-experimental.yml:
  dispatch-only from main through the protected composer-experimental
  environment, with its own token (CLOUDFLARE_API_TOKEN_EXPERIMENTAL). Before
  every deploy it checks that anonymous GET and POST requests already go to
  the Access login, and it records the rollback anchor or refuses. There is
  no first deploy.
- check-beta-deploy.mjs -> check-experimental-deploy.mjs: both configs are
  the one composer Worker with one custom-domain route and no custom-domain
  Preview URLs, equal apart from the artifact. The Access check adds an
  empty-body POST to /api/propose, and refuses any failure to reach the
  hostname.
- Tests: experimental-deploy.test.mjs, written fail-first (59 assertions
  failed against the old guard, configs and workflow), and the operator-log
  config check.
- Docs: deployment.md's single-host runbook replaces the superseded beta
  section. BLOCKS.md, BETA-DEPLOY-PLAN.md, BETA-OPERATIONS.md, the decision
  record, the plan's status note and backlog rows O2, O10 and O12 are
  updated.

No application, Worker code, block code or request builder changes. No new
hostname, subdomain or Worker is created or named.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R6Ax8qvfN3gYUC85oSGnoi
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 17, 2026 •

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
dspack-studio-composer 9908313 Oct 17 2026, 07:15 PM

…loys, guard, tests)

An independent review of 3164245 found no material defect and seven minor
ones. This commit fixes six, written fail-first:
- The anchor is read as JSON. `wrangler deployments status --json` is parsed
  by the guard's new --active-version mode, and only the one version the
  latest deployment serves at 100% is accepted. A gradual split or a listing
  refuses, and the summary records "anchor: <id>".
- A partial deploy is still checked. wrangler makes the new version live
  before it publishes the custom domain's settings, so the deploy step has an
  id, and the record and post-deploy Access check run whenever it ran, even
  when it failed.
- readConfig refuses a "__proto__" key at any depth, escaped or not. The
  parser turns one into a prototype that Object.keys never shows and wrangler
  still reads.
- Both configs set previews_enabled false on the custom-domain route, so
  every deploy turns the custom domain's own Preview URLs off. wrangler
  4.146.0 and 4.151.1 read both configs offline with no warning.
- The workflow tests now fail on each regression the review showed passing:
  if: always(), || true, a workflow-level token, an extra --var or --route,
  a softened step condition, and a dropped persist-credentials.
- The --access and --active-version modes load nothing from node_modules.

The seventh, the job split that would make the token boundary real, is
recorded as backlog O14. The same holds for Workers Builds' own build and
deploy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R6Ax8qvfN3gYUC85oSGnoi
…klog rows

The reviewer's verification of 721242c resolved five of the seven findings
and confirmed the deferral of the sixth. Its one material item, NEW-1, was
conditional: Workers Builds' own wrangler must accept previews_enabled on
the route. It does: the Workers Builds branch build of 721242c, which reads
this same wrangler.jsonc, succeeded.

This commit closes NEW-2, the two test gaps it found. Both mutations now
fail a test, and the seven earlier ones still do:
- exactly one step may deploy, and after the guard only the two post-deploy
  steps may carry a condition, so a retry step with if: failure() fails;
- a workflow-level env written in flow style fails like a block one.

Backlog: O14 now names the repository's own guard script among what build
code could rewrite. New row O15 pins the wrangler Workers Builds runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R6Ax8qvfN3gYUC85oSGnoi
@ryandmonk
ryandmonk marked this pull request as ready for review October 18, 2026 02:20
Copilot AI balanced review requested due to automatic review settings October 18, 2026 02:20
@ryandmonk
ryandmonk merged commit 8053151 into main Oct 18, 2026
3 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The production-host cutover requires the owner to verify and record Access steps A–E before merging.

No open issues 🎉

What changed in this PR

Prepares the invite-only experimental build for the existing Composer Worker behind Cloudflare Access, without changing application code or creating another host.

Changes:

  • Hardens both deployment configs while preserving the block-free default artifact.
  • Replaces the separate-beta workflow with mandatory Access checks and rollback-version recording.
  • Updates regression tests and single-host operational documentation.
File Description
wrangler.jsonc Disables alternate URLs and preserves dashboard variables.
wrangler.composer-experimental.jsonc Targets the existing Worker with experimental assets.
wrangler.composer-beta.jsonc Removes the separate-Worker config.
docs/​deployment.md Documents single-host deployment, switches and rollback.
docs/​blocks/​RELEASE-REVIEW.md Updates test references.
docs/​blocks/​POST-BETA-BACKLOG.md Records completed safeguards and deferred hardening.
docs/​blocks/​PHASE-2-RELEASE-PLAN.md Updates deployment-path status.
docs/​blocks/​PHASE-2-DECISION-RECORD.md Records replacement of the separate-beta path.
docs/​blocks/​BETA-OPERATIONS.md Clarifies variable persistence and rollback behavior.
docs/​blocks/​BETA-DEPLOY-PLAN.md Updates cutover checks and deployment safeguards.
docs/​BLOCKS.md Describes single-host block-delivery controls.
apps/​composer/​worker/​operator-log.test.mjs Checks both configs preserve dashboard variables.
apps/​composer/​scripts/​check-experimental-deploy.mjs Validates configs, artifacts, Access and active versions.
apps/​composer/​scripts/​check-beta-deploy.mjs Removes the superseded guard.
apps/​composer/​lib/​experimental-deploy.test.mjs Tests the single-host deployment safeguards.
apps/​composer/​lib/​beta-deploy.test.mjs Removes the superseded tests.
.github/​workflows/​deploy-composer-experimental.yml Adds guarded deployment to the existing Worker.
.github/​workflows/​deploy-composer-beta.yml Removes the separate-beta workflow.

Review effort: Balanced

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.

3 participants