From 56523396761444bb2d018cf5fb5408bdc424afb4 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 4 Sep 2026 22:25:57 -0400 Subject: [PATCH] Record completed internal CMS rollout --- README.md | 99 +++++++++++++++++-------- docs/content-model.md | 22 ++++-- docs/decap-cms-gitea-setup.md | 75 ++++++++++--------- docs/decap-cms-pilot.md | 134 +++++++++++++++++++++------------- docs/preview-deployment.md | 46 +++++++----- 5 files changed, 234 insertions(+), 142 deletions(-) diff --git a/README.md b/README.md index 850383b..8f40dc2 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,66 @@ -# Happy Tails Astro pilot +# Happy Tails Astro sites and internal CMS -This repository is a static Astro pilot for `3031023.xyz`. The existing -Cloudflare Pages site remains the rollback target while this branch is tested. +This repository backs the production Happy Tails sites at `3031023.xyz` and +`blog.3031023.xyz`. Both are static Astro builds deployed as separate +Cloudflare Workers. The retained Cloudflare Pages project remains the primary +site rollback target. -The planned preview-only editing experiment is documented in the [Decap CMS -Gitea pilot](docs/decap-cms-pilot.md). It does not authorize CMS activation, a -production merge, or a domain change. +The Decap CMS workflow is operational for internal SloanOps use. It is not a +public feature or a client-editing offer. The approved internal editor is +`sloanops.admin`; do not invite additional editors without a separate access, +support, and security review. ## Repository layout -- `sites/www/` is the primary-site content location; it serves `3031023.xyz`. -- `sites/blog/` is the separate `blog.3031023.xyz` sample site. -- `sites.json` is the operator-controlled manifest. It is not public intake. +- `sites/www/` contains primary-site Markdown and site data. +- `sites/blog/` contains the Journal Markdown and site data. +- `public/admin/` contains the shared, no-index Decap admin shell and config. +- `public/images/uploads/` is the only CMS-writable media directory. +- `sites.json` is the operator-controlled build manifest, not public intake. - `src/` contains shared Astro presentation code. -- `schemas/` and `fixtures/` provide the Phase 1 JSON contract proof. +- `schemas/`, `scripts/`, and `fixtures/` enforce the content and deployment + contracts. -Blog posts and primary-site FAQs are now static Markdown collections with -validated frontmatter and draft filtering. Production CMS publishing, public -sign-up changes, n8n changes, and onboarding-worker changes remain out of -scope; the linked Decap plan is a preview-only editing experiment. +## Internal CMS workflow + +Use `https://3031023.xyz/admin/` for normal internal editing. Decap commits +content and approved raster media directly to the protected `cms` staging +branch; it never writes to `main`. + +1. Make and review content changes on `cms`. +2. Confirm the `cms` Worker version previews and content validation results. +3. Open a pull request from `cms` to `main`. +4. Merge only after the required CMS validation, both site builds, and SloanOps + review pass. +5. Relevant `main` changes deploy the production primary and/or blog Worker. +6. Synchronize the resulting `main` merge commit back into `cms` through a + protected-branch pull request before the next editing cycle. + +Operator-authored CMS application or configuration work also starts from the +current `cms` state. Use a short branch and a pull request into protected +`cms`, then promote the reviewed `cms` state to `main`. Direct protected-file +pushes to `cms` and all direct pushes to `main` are intentionally rejected. + +Only the stable CMS and production admin origins are registered with Gitea. +Logging in from a branch-specific Worker preview alias therefore produces +`Unregistered Redirect URI`; this is expected. Never solve it with a wildcard +OAuth redirect or wildcard CORS origin. Use branch previews to inspect rendered +site output and an approved admin origin to test editing. + +See the [CMS operating record](docs/decap-cms-pilot.md) and +[Gitea settings](docs/decap-cms-gitea-setup.md) for the complete boundaries. + +## Content and media behavior + +The CMS exposes primary FAQs, services, team members, fixed primary page +images, and Journal posts. Draft content is excluded from pages and sitemaps. +Journal publish and optional updated dates use Decap's supported `datetime` +widget in date-only UTC mode; new posts default the publish date to the current +UTC date. + +CMS image sources must be approved JPG, PNG, WebP, or AVIF files in the flat +uploads directory and may be at most 25 MiB. Every build re-encodes public +copies to at most 1920 pixels and 500 KiB and strips embedded metadata. ## Local checks @@ -37,25 +79,18 @@ Each selected site builds to `dist//`. The Worker configurations use only static assets and a custom 404 page. They contain no custom domains, routes, account IDs, or secrets. -`npm run verify:cms` checks the fixed Gitea branch/backend, the four Markdown -content contracts, the fixed page-image record, the bounded raster-media boundary, the pinned admin -shell, and the stable secret-free pull-request status context. The live steps -remain unapplied; see the [Gitea pilot settings](docs/decap-cms-gitea-setup.md) -for the exact review checklist. +`npm run verify:cms` checks the fixed Gitea backend and `cms` branch, all CMS +content contracts, media boundaries, the pinned admin shell, supported +widgets, and the stable secret-free pull-request status context. ## Preview deployment -The Gitea workflows upload scoped non-production preview versions of the -production Workers from every non-`main` branch. Cloudflare assigns each branch -a readable `workers.dev` preview alias without changing production traffic. -Relevant primary-site pushes to `main` deploy `3031023-xyz-www`, and relevant -blog pushes to `main` deploy `3031023-xyz-blog`, automatically. Both production -workflows deploy static assets only. -It uses the existing `CF_API_TOKEN` and `CF_ACCOUNT_ID`. The token retains the -Pages permissions needed for the `main` rollback target and now also permits -the non-production Worker preview deployment. -Routine jobs do not configure DNS or custom domains. +Non-`main` pushes upload no-index Cloudflare Worker versions with readable +branch aliases; they do not move production traffic. Relevant `main` pushes +deploy `3031023-xyz-www` and/or `3031023-xyz-blog` automatically after building +and verifying the selected site. Routine workflows contain no DNS, custom +domain, route, account ID, or token configuration. -Production hostname attachment and the `www` redirect are manual, -operator-controlled cutover steps. See `docs/pilot-baseline.md` for the -rollback record. +The production domain bindings, `www` redirect, retained Pages rollback, and +recovery steps are recorded in the [primary cutover runbook](docs/cutover-runbook.md) +and [blog production runbook](docs/blog-production-runbook.md). diff --git a/docs/content-model.md b/docs/content-model.md index ffa6d36..cadd8a6 100644 --- a/docs/content-model.md +++ b/docs/content-model.md @@ -19,13 +19,21 @@ Phase 6 adds two validated Markdown collections: homepage. Both collections require frontmatter and support `draft: true`. Draft entries -are excluded from the rendered pages and sitemap. This is a Git-backed pilot; -it does not add a CMS or client-editing access. +are excluded from the rendered pages and sitemap. The internal Decap CMS now +edits these collections through the protected `cms` staging branch; client and +public editing remain disabled. -Images must live under `public/images/`, be referenced with descriptive alt -text where rendered, and remain at or below the 500 KiB pilot limit. The build -will reject malformed site JSON and oversize source images. +Journal `pubDate` and optional `updatedDate` fields use Decap's supported +`datetime` control in date-only UTC mode. New posts default `pubDate` to the +current UTC date. Repository verification rejects the unsupported `date` +widget so the missing-control failure cannot silently return. + +CMS images must live in the flat `public/images/uploads/` directory and be +referenced with descriptive alt text where rendered. Approved source rasters +may be at most 25 MiB; each build produces a metadata-free public copy no +larger than 1920 pixels or 500 KiB. The build rejects malformed site JSON, +disallowed image signatures, nested upload paths, and oversized sources. Each selected build also generates `sitemap.xml` and `robots.txt`. Preview -builds disallow indexing; a future approved production build must set -`DEPLOY_ENV=production` to publish crawlable robots rules and the sitemap URL. +builds disallow indexing; production workflows set `DEPLOY_ENV=production` to +publish crawlable robots rules and the sitemap URL. diff --git a/docs/decap-cms-gitea-setup.md b/docs/decap-cms-gitea-setup.md index 4e045cb..daaa8e7 100644 --- a/docs/decap-cms-gitea-setup.md +++ b/docs/decap-cms-gitea-setup.md @@ -1,9 +1,9 @@ # Decap CMS: Gitea settings -Status: the preview-only pilot was accepted for promotion on 2026-09-04. The -repository now targets the permanent `cms` staging branch. Update the exact -CORS origin and PKCE OAuth redirect only after its first preview deployment -confirms the predicted alias. Nothing has been merged to `main`. +Status: applied and verified for internal use on 2026-09-04. The repository +targets the permanent `cms` staging branch, the CMS application is deployed +from `main`, and `sloanops.admin` is the approved internal editor. Client or +public access remains out of scope. The repository implementation targets these fixed public values: @@ -14,7 +14,8 @@ The repository implementation targets these fixed public values: | Gitea base URL | `https://git.sloanops.com` | | Gitea API root | `https://git.sloanops.com/api/v1` | | Stable CMS origin | `https://cms-3031023-xyz-www.ksloan90.workers.dev` | -| Exact OAuth redirect URI | `https://cms-3031023-xyz-www.ksloan90.workers.dev/admin/` | +| Production admin origin | `https://3031023.xyz` | +| Exact OAuth redirects | `https://cms-3031023-xyz-www.ksloan90.workers.dev/admin/` and `https://3031023.xyz/admin/` | | Required status context | `CMS content validation / cms-content-validation (pull_request)` | The pilot and permanent branch names are intentionally slash-free. Decap @@ -24,10 +25,10 @@ presented as an empty collection. The permanent `cms` branch is still a non-production staging branch: changes reach `main` only through the protected pull-request flow. -The Workers alias was confirmed after the first approved preview deployment. -If Cloudflare later assigns a different stable origin, -update the repository configuration, CORS origin, and OAuth redirect URI -together through review before enabling login. +The stable Worker alias and production admin route were confirmed. If either +origin changes, update repository configuration, CORS, and OAuth together +through review. Branch-specific preview aliases are intentionally not +registered and will return `Unregistered Redirect URI` during login. ## Runner cache settings @@ -53,24 +54,23 @@ action branches on every job; npm packages remain keyed separately by ## Applied CORS policy The tracked Gitea service in -`/home/sloan/gitRepos/SloanOps/infrastructure/docker-compose.yml` contains -these entries in the existing `gitea.environment` list and was deployed -through the normal infrastructure change process: +`/home/sloan/gitRepos/SloanOps/infrastructure/docker-compose.yml` was deployed +with exact origins for the internal CMS applications. The two origins relevant +to this repository are represented by these settings: ```yaml - GITEA__cors__ENABLED=true -- GITEA__cors__ALLOW_DOMAIN=https://cms-3031023-xyz-www.ksloan90.workers.dev +- GITEA__cors__ALLOW_DOMAIN=https://cms-3031023-xyz-www.ksloan90.workers.dev,https://3031023.xyz - GITEA__cors__METHODS=GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS - GITEA__cors__MAX_AGE=10m - GITEA__cors__ALLOW_CREDENTIALS=false - GITEA__cors__HEADERS=Authorization,Content-Type ``` -This is one exact origin, no wildcard, no cookie-bearing cross-origin request, -and only the API methods and non-safelisted request headers used by the Gitea -backend. Do not add the production domains, the journal preview, arbitrary -Workers preview hosts, or localhost. Gitea's same-origin login page continues -to use its own session cookie; Decap API calls use the OAuth bearer token. +These are exact origins, not wildcards, and API requests do not carry cookies. +Do not add arbitrary Worker preview hosts or localhost. Gitea's same-origin +login page continues to use its own session cookie; Decap API calls use the +OAuth bearer token. After an approved Gitea deployment, verify a preflight without including a token or cookie: @@ -94,11 +94,11 @@ CORS response were verified: - Application name: `3031023.xyz Decap CMS`. - Confidential client: **unchecked**. The browser client must use PKCE. -- Redirect URI: exactly - `https://cms-3031023-xyz-www.ksloan90.workers.dev/admin/`, including the - trailing slash. -- Do not add wildcards, localhost, either production hostname, or the journal - preview as additional redirects. +- Redirect URIs: exactly + `https://cms-3031023-xyz-www.ksloan90.workers.dev/admin/` and + `https://3031023.xyz/admin/`, including trailing slashes. +- Do not add wildcards, localhost, the journal hostname, or branch-specific + Worker preview aliases as additional redirects. - Copy only the public client ID into `backend.app_id` in `public/admin/config.yml` through a reviewed commit. - Do not store or use a client secret in the repository, Actions, Cloudflare, @@ -158,6 +158,11 @@ merge, administrator, or bypass allowlists. | Protection bypass allowlist | Enabled only for the named SloanOps operator user/team | | CMS/client account | Write access, but absent from every allowlist | +Do not assume a local Git identity can use the operator bypass. A direct +protected-file push was rejected during the final Journal repair sync, and the +successful path was a short branch followed by PR #11 into `cms`. Prefer that +reviewable path for all operator-authored CMS code and configuration changes. + Enter this exact single value in **Unprotected file patterns**: ```text @@ -197,18 +202,22 @@ Workflows, source, scripts, package files, schemas, manifests, Wrangler configuration, `site.json` files, and the CMS application therefore cannot be altered by choosing another branch name. -## Verification before an editor invitation +## Routine verification and promotion 1. Run `npm ci`, `npm run verify:cms`, and both site build/verifier pairs. -2. Create `cms` from the reviewed baseline only after approval. -3. Apply both protection rules together and confirm `main` is priority 1 and - `*` is priority 2. -4. Use the same non-operator account intended for Decap to prove allowed - Markdown edits succeed and every protected-path/direct-`main` attempt fails. -5. Open, but do not merge, a pull request into `main`. Confirm Gitea reports - the exact required status context above and blocks merge without both its - successful result and a current SloanOps approval. -6. Confirm both branch previews remain no-index and production is unchanged. +2. For CMS application/configuration work, branch from current `cms`, open a + pull request back into `cms`, and review the no-index version previews. + The required CMS status is scoped to pull requests into `main`, so the local + full check is mandatory before this staging merge. +3. Open `cms` to `main` and confirm Gitea reports the exact required status + context above and blocks merge without its successful result and current + SloanOps approval. +4. Merge deliberately, then confirm each affected production Worker workflow + succeeds and smoke-test the public routes. +5. Merge the resulting `main` commit back into `cms` through another + protected-branch pull request if the histories diverge. +6. Confirm previews remain no-index and never expand OAuth/CORS for a temporary + branch alias. Do not treat a Decap UI restriction as authorization. Gitea's first-match branch rules are the enforcement boundary, and the pull-request build is the diff --git a/docs/decap-cms-pilot.md b/docs/decap-cms-pilot.md index a1ac224..ee61b0b 100644 --- a/docs/decap-cms-pilot.md +++ b/docs/decap-cms-pilot.md @@ -1,19 +1,21 @@ # Decap CMS Gitea pilot and promotion -Status: the preview-only pilot was accepted for promotion to the permanent -`cms` staging branch on 2026-09-04. Repository configuration now targets the -predicted stable `cms` preview origin. CORS and OAuth must be changed together -only after that first preview confirms the alias. Nothing has been merged to -`main`, and production remains unchanged. +Status: completed and operational for internal SloanOps use as of 2026-09-04. +The reviewed CMS application is deployed from `main`, Decap writes only to the +protected `cms` staging branch, and the final Journal date-control repair is +present on both branches. Production deployments from the approved merge +completed successfully. + +This remains an internal capability for `sloanops.admin`, not a public feature +or a client-editing offer. No client account has been invited. Updated: 2026-09-04 ## Purpose -Use the `3031023.xyz` demonstration repository to determine whether -Decap CMS can provide a useful editing interface for all sites owned under one -root domain without allowing an editor to publish directly to production or -modify deployment code. +Record and operate the Decap CMS editing interface for both sites in the +`3031023.xyz` demonstration repository without allowing an editor to publish +directly to production or modify deployment code. The pilot covers both the primary Happy Tails site and its journal subdomain. That matches the SloanOps repository boundary: one private repository per root @@ -21,8 +23,9 @@ domain, with independently built sites beneath `sites/`. A future client who owns the root-domain site also owns the hosted subdomains represented in that repository. -This plan does not authorize a production merge, domain change, contact-form -activation, repository invitation, or removal of a rollback resource. +The completed internal rollout does not authorize public discovery, a client +repository invitation, contact-form changes, a broader OAuth grant, or removal +of a rollback resource. ## Current evidence @@ -41,6 +44,13 @@ activation, repository invitation, or removal of a rollback resource. writes directly to the configured branch. - The Gitea backend does not support Git LFS. Pilot media must remain small, ordinary repository assets. +- The approved `sloanops.admin` account can authenticate at the production + admin route, load all collections, and write through the `cms` branch. +- Gitea rejects protected-file pushes to `cms` and direct pushes to `main`; + operator code/configuration changes therefore use pull requests as well. +- Arbitrary branch-preview hostnames are deliberately absent from the OAuth + redirect and CORS allowlists. Login from one returns `Unregistered Redirect + URI`; rendered branch previews remain usable without expanding OAuth. References: @@ -56,8 +66,8 @@ recorded in [Decap CMS pilot: Gitea settings](decap-cms-gitea-setup.md). ## Repository implementation -The reviewed pilot implementation is the baseline for the permanent `cms` -staging branch: +The reviewed production implementation and permanent `cms` staging branch +provide the following controls: - `public/admin/` provides a permanently no-index admin shell and one shared configuration for both site builds. @@ -68,6 +78,10 @@ staging branch: - All four Markdown content collections and every persisted frontmatter field are modeled with the current Astro/Zod bounds. Markdown controls remain text-first; embedded images and general file widgets are absent. +- Journal publish and optional updated dates use the supported Decap + `datetime` widget in date-only UTC mode. New posts default to the current UTC + date, and the verifier rejects the unsupported `date` widget that caused the + original create/update failures. - A fixed image-only Markdown record controls the primary home, About, Services, and Contact page imagery without exposing either `site.json`. - Team and page-image fields accept optional bounded uploads. Editors can add, @@ -94,11 +108,11 @@ staging branch: repository cache without trusting a mutable tag or refreshing GitHub on every job. -The pilot dependencies were verified on `cms-pilot`. The same protection rules -apply to `cms`; its exact-origin CORS and OAuth values are the remaining -operator-controlled promotion step. +The pilot dependencies were verified on `cms-pilot`, promoted to the permanent +`cms` branch, and deployed to `main`. Exact-origin CORS, PKCE OAuth, branch +protection, previews, validation, and production deployment are all applied. -## Proposed pilot architecture +## Operating architecture ```text Decap at /admin/ @@ -126,16 +140,17 @@ is acceptable because both sites belong to the same root-domain repository and the same owner. One Decap configuration exposes clearly labeled Primary Site and Journal collection groups. -Predicted preview URLs for branch `cms`: +Confirmed stable preview URLs for branch `cms`: - `https://cms-3031023-xyz-www.ksloan90.workers.dev/admin/` - `https://cms-3031023-xyz-www.ksloan90.workers.dev/` - `https://cms-3031023-xyz-blog.ksloan90.workers.dev/` -Confirm the aliases from the successful Gitea Actions run rather than treating -these predicted URLs as authoritative. +The production admin entry is `https://3031023.xyz/admin/`. Use the stable +`cms` admin origin or the production admin entry for interactive CMS testing; +do not use arbitrary branch aliases for OAuth login. -## Initial Decap scope +## Internal Decap scope Allow editing and creation only within these collections: @@ -248,34 +263,30 @@ Preview deployment remains branch-specific and may use the existing Worker workflows. Cloudflare credentials stay scoped to the deployment step; content validation does not need them. -Because Decap writes to a fixed `cms` branch, SloanOps creates the pull -request in Gitea. After an approved merge, synchronize the CMS branch with -`main` before the next editing cycle. Do not automate production merge. +Because Decap writes to a fixed `cms` branch, SloanOps creates the pull request +from `cms` to `main` in Gitea. Internal CMS application/configuration changes +start from the current `cms` state, enter protected `cms` through an +operator-reviewed pull request, and are then promoted with the staged content. +After an approved `main` merge, synchronize its merge commit back into `cms` +through a protected-branch pull request before the next editing cycle. Do not +automate production merge. The accepted pilot is promoted by replacing the disposable branch with the permanent, slash-free `cms` branch. It remains a preview/staging branch and retains the same pull-request, validation, and approval path into `main`. -## Gitea and Decap setup +## Routine operating sequence -1. Add a non-confidential Gitea OAuth application for the exact preview - `/admin/` redirect URI or URIs. Store only the public client ID in Decap. -2. Enable Gitea API CORS for the exact approved preview origin or origins. - Allow only required methods and headers, including `Authorization` and - `Content-Type`; do not use a wildcard origin. -3. Create `cms` from the reviewed repository baseline. -4. Review the pinned Decap admin assets beneath `public/admin/`. Do not replace - the exact version and integrity value with a floating range. -5. Configure the Gitea backend for `SloanOps/3031023-xyz` and branch - `cms`. -6. Model every persisted frontmatter field with limits matching - `src/content.config.ts`. Do not let a CMS save silently remove an existing - field. -7. Apply and verify the `main` and fallback branch protections before inviting - or testing a non-operator editor. -8. Add the secret-free pull-request validation workflow. -9. Push only the review branch and confirm both no-index preview builds before - any CMS login test. +1. Edit through an approved admin origin and confirm the commit lands only on + `cms` in an allowed content or media path. +2. Confirm the affected no-index Worker version preview succeeds. +3. Run the local CMS verifier and both site build/verifier pairs before any + operator-authored CMS application or configuration PR into `cms`. +4. Review the complete staged diff and open `cms` to `main`. +5. Require the stable secret-free validation status and SloanOps approval. +6. Merge deliberately and confirm the affected production Worker jobs pass. +7. Synchronize the resulting `main` merge commit back into protected `cms` + before another editing cycle. ## Test matrix @@ -305,13 +316,36 @@ not record OAuth tokens or Cloudflare credentials. | DC-19 | Refresh, sign out, reauthenticate, and let a session expire | Session behavior is understandable and no unsaved-content loss is observed. | | DC-20 | Review commit attribution and audit history | Gitea identifies the editor and the exact content/media changes. | -Do not merge DC-15 during the initial infrastructure and editing test. A later -harmless demo-content merge requires explicit approval and repeats both public -site checks after deployment. +The initial no-merge restriction applied only while the pilot was being +evaluated. Promotion is now allowed through the protected workflow above; each +release still requires explicit review and repeats both public site checks. -## Acceptance criteria +## Completion record — 2026-09-04 -Accept the pilot only when: +- PR #1 launched the no-index Decap shell and Gitea-backed collections. +- PRs #2 and #3 tightened image filename validation and added bounded, + metadata-stripping build optimization. +- PRs #4 and #5 synchronized the reviewed implementation into `cms` and then + promoted it to `main`. +- Disposable Journal, page-image, and FAQ edits exposed workflow and schema + behavior; the test content was reverted and the temporary FAQ was removed. +- PR #9 consolidated the rollback history and resynchronized the branches. +- Failed Journal content runs were traced to Decap's unsupported `date` widget. + PR #10 replaced it with date-only `datetime` controls, defaulted new publish + dates, and added a verifier regression guard. +- PR #11 synchronized the final repair into protected `cms` after the direct + protected-file push was correctly rejected. +- CMS validation and both site builds passed locally. The PR #10 required + validation passed, and production blog run 342 plus primary run 343 both + completed successfully. +- The deployed Journal editor was opened at `3031023.xyz/admin/`; Publish date + displayed a populated UTC date and Updated date displayed an optional date + control. No post was saved during this final smoke test. + +## Operating acceptance boundary + +The internal pilot was accepted against these controls. Re-evaluate them before +any client rollout: - both site families are understandable in one CMS; - content edits preserve every modeled field and pass Astro validation; @@ -329,7 +363,7 @@ account control over executable deployment files. ## Rollback and cleanup -If the pilot is rejected or paused: +If the internal CMS is paused: 1. Revert or delete disposable changes on `cms`. 2. Delete the two disposable preview aliases/versions if desired; do not delete @@ -341,7 +375,7 @@ If the pilot is rejected or paused: 6. Remove or retain branch protections deliberately; never weaken `main` as cleanup. -## Implementation estimate +## Historical implementation estimate Expected hands-on implementation time: **6–9 hours**, normally one focused working day. Budget up to two elapsed days if OAuth/CORS behavior or Gitea diff --git a/docs/preview-deployment.md b/docs/preview-deployment.md index e9e9c06..585b105 100644 --- a/docs/preview-deployment.md +++ b/docs/preview-deployment.md @@ -1,24 +1,30 @@ -# Preview deployment and later cutover controls +# Worker version previews -The preview workflow deploys static output only. It has two deliberately -separate Worker names: +Non-`main` pushes build and verify the affected site, then upload a +non-production version of its production Worker with a readable branch alias. +They do not deploy the version to production traffic. -| Site key | Preview Worker | Future hostname | -| --- | --- | --- | -| `www` | `3031023-xyz-www-preview` | `3031023.xyz` | -| `blog` | `3031023-xyz-blog-preview` | `blog.3031023.xyz` | +| Site key | Versioned Worker | Stable `cms` alias | Production hostname | +| --- | --- | --- | --- | +| `www` | `3031023-xyz-www` | `cms-3031023-xyz-www.ksloan90.workers.dev` | `3031023.xyz` | +| `blog` | `3031023-xyz-blog` | `cms-3031023-xyz-blog.ksloan90.workers.dev` | `blog.3031023.xyz` | -`workers.dev` is the only preview destination. Neither Wrangler configuration -contains a route, custom domain, zone ID, account ID, or token. Cloudflare -credentials remain Gitea repository secrets. The preview workflow uses the -existing `CF_API_TOKEN`, which has Account Workers Scripts edit/write for this -pilot plus the retained Pages rollback permissions. It uses `CF_ACCOUNT_ID` as -the account selector. Do not grant DNS edit permissions for routine preview -deployment. +The workflows derive the preview alias from the branch name, build with +preview robots rules, run output verification, and call `wrangler versions +upload`. They use the existing `CF_API_TOKEN` and `CF_ACCOUNT_ID` repository +secrets but contain no DNS, routes, custom-domain mutations, account IDs, or +tokens in source. -The primary Worker must pass its public preview test matrix before an operator -records the exact Pages deployment/DNS state and requests production-cutover -approval. A production deployment should use a separately named approved -Worker or an explicitly promoted Worker version; it must not be an ordinary -push job. Attaching `3031023.xyz`, configuring the `www.3031023.xyz` redirect, -and attaching `blog.3031023.xyz` each remain manual approval-boundary actions. +Only the stable CMS and production admin origins are registered with Gitea +OAuth/CORS. A CMS login attempt from a temporary branch alias is expected to +fail with `Unregistered Redirect URI`; do not add a wildcard. Review rendered +site output on the branch alias and use `https://3031023.xyz/admin/` or the +stable `cms` admin origin for editing tests. + +Relevant pushes to `main` use separate production workflows and `wrangler +deploy` for the affected Worker after a production build and verifier pass. +The existing domain bindings do not change during routine deployments. + +The older `*-preview` Wrangler configurations and Workers are retained as +rollback artifacts. Current branch workflows use Worker version previews of +the production-named Workers instead.