Record completed internal CMS rollout

This commit is contained in:
Kevin
2026-09-04 22:25:57 -04:00
parent dc3ec3ff30
commit 5652339676
5 changed files with 234 additions and 142 deletions
+67 -32
View File
@@ -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 This repository backs the production Happy Tails sites at `3031023.xyz` and
Cloudflare Pages site remains the rollback target while this branch is tested. `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 The Decap CMS workflow is operational for internal SloanOps use. It is not a
Gitea pilot](docs/decap-cms-pilot.md). It does not authorize CMS activation, a public feature or a client-editing offer. The approved internal editor is
production merge, or a domain change. `sloanops.admin`; do not invite additional editors without a separate access,
support, and security review.
## Repository layout ## Repository layout
- `sites/www/` is the primary-site content location; it serves `3031023.xyz`. - `sites/www/` contains primary-site Markdown and site data.
- `sites/blog/` is the separate `blog.3031023.xyz` sample site. - `sites/blog/` contains the Journal Markdown and site data.
- `sites.json` is the operator-controlled manifest. It is not public intake. - `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. - `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 ## Internal CMS workflow
validated frontmatter and draft filtering. Production CMS publishing, public
sign-up changes, n8n changes, and onboarding-worker changes remain out of Use `https://3031023.xyz/admin/` for normal internal editing. Decap commits
scope; the linked Decap plan is a preview-only editing experiment. 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 ## Local checks
@@ -37,25 +79,18 @@ Each selected site builds to `dist/<site-key>/`. The Worker configurations use
only static assets and a custom 404 page. They contain no custom domains, only static assets and a custom 404 page. They contain no custom domains,
routes, account IDs, or secrets. routes, account IDs, or secrets.
`npm run verify:cms` checks the fixed Gitea branch/backend, the four Markdown `npm run verify:cms` checks the fixed Gitea backend and `cms` branch, all CMS
content contracts, the fixed page-image record, the bounded raster-media boundary, the pinned admin content contracts, media boundaries, the pinned admin shell, supported
shell, and the stable secret-free pull-request status context. The live steps widgets, and the stable secret-free pull-request status context.
remain unapplied; see the [Gitea pilot settings](docs/decap-cms-gitea-setup.md)
for the exact review checklist.
## Preview deployment ## Preview deployment
The Gitea workflows upload scoped non-production preview versions of the Non-`main` pushes upload no-index Cloudflare Worker versions with readable
production Workers from every non-`main` branch. Cloudflare assigns each branch branch aliases; they do not move production traffic. Relevant `main` pushes
a readable `workers.dev` preview alias without changing production traffic. deploy `3031023-xyz-www` and/or `3031023-xyz-blog` automatically after building
Relevant primary-site pushes to `main` deploy `3031023-xyz-www`, and relevant and verifying the selected site. Routine workflows contain no DNS, custom
blog pushes to `main` deploy `3031023-xyz-blog`, automatically. Both production domain, route, account ID, or token configuration.
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.
Production hostname attachment and the `www` redirect are manual, The production domain bindings, `www` redirect, retained Pages rollback, and
operator-controlled cutover steps. See `docs/pilot-baseline.md` for the recovery steps are recorded in the [primary cutover runbook](docs/cutover-runbook.md)
rollback record. and [blog production runbook](docs/blog-production-runbook.md).
+15 -7
View File
@@ -19,13 +19,21 @@ Phase 6 adds two validated Markdown collections:
homepage. homepage.
Both collections require frontmatter and support `draft: true`. Draft entries Both collections require frontmatter and support `draft: true`. Draft entries
are excluded from the rendered pages and sitemap. This is a Git-backed pilot; are excluded from the rendered pages and sitemap. The internal Decap CMS now
it does not add a CMS or client-editing access. 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 Journal `pubDate` and optional `updatedDate` fields use Decap's supported
text where rendered, and remain at or below the 500 KiB pilot limit. The build `datetime` control in date-only UTC mode. New posts default `pubDate` to the
will reject malformed site JSON and oversize source images. 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 Each selected build also generates `sitemap.xml` and `robots.txt`. Preview
builds disallow indexing; a future approved production build must set builds disallow indexing; production workflows set `DEPLOY_ENV=production` to
`DEPLOY_ENV=production` to publish crawlable robots rules and the sitemap URL. publish crawlable robots rules and the sitemap URL.
+42 -33
View File
@@ -1,9 +1,9 @@
# Decap CMS: Gitea settings # Decap CMS: Gitea settings
Status: the preview-only pilot was accepted for promotion on 2026-09-04. The Status: applied and verified for internal use on 2026-09-04. The repository
repository now targets the permanent `cms` staging branch. Update the exact targets the permanent `cms` staging branch, the CMS application is deployed
CORS origin and PKCE OAuth redirect only after its first preview deployment from `main`, and `sloanops.admin` is the approved internal editor. Client or
confirms the predicted alias. Nothing has been merged to `main`. public access remains out of scope.
The repository implementation targets these fixed public values: 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 base URL | `https://git.sloanops.com` |
| Gitea API root | `https://git.sloanops.com/api/v1` | | Gitea API root | `https://git.sloanops.com/api/v1` |
| Stable CMS origin | `https://cms-3031023-xyz-www.ksloan90.workers.dev` | | 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)` | | Required status context | `CMS content validation / cms-content-validation (pull_request)` |
The pilot and permanent branch names are intentionally slash-free. Decap 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 non-production staging branch: changes reach `main` only through the protected
pull-request flow. pull-request flow.
The Workers alias was confirmed after the first approved preview deployment. The stable Worker alias and production admin route were confirmed. If either
If Cloudflare later assigns a different stable origin, origin changes, update repository configuration, CORS, and OAuth together
update the repository configuration, CORS origin, and OAuth redirect URI through review. Branch-specific preview aliases are intentionally not
together through review before enabling login. registered and will return `Unregistered Redirect URI` during login.
## Runner cache settings ## Runner cache settings
@@ -53,24 +54,23 @@ action branches on every job; npm packages remain keyed separately by
## Applied CORS policy ## Applied CORS policy
The tracked Gitea service in The tracked Gitea service in
`/home/sloan/gitRepos/SloanOps/infrastructure/docker-compose.yml` contains `/home/sloan/gitRepos/SloanOps/infrastructure/docker-compose.yml` was deployed
these entries in the existing `gitea.environment` list and was deployed with exact origins for the internal CMS applications. The two origins relevant
through the normal infrastructure change process: to this repository are represented by these settings:
```yaml ```yaml
- GITEA__cors__ENABLED=true - 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__METHODS=GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS
- GITEA__cors__MAX_AGE=10m - GITEA__cors__MAX_AGE=10m
- GITEA__cors__ALLOW_CREDENTIALS=false - GITEA__cors__ALLOW_CREDENTIALS=false
- GITEA__cors__HEADERS=Authorization,Content-Type - GITEA__cors__HEADERS=Authorization,Content-Type
``` ```
This is one exact origin, no wildcard, no cookie-bearing cross-origin request, These are exact origins, not wildcards, and API requests do not carry cookies.
and only the API methods and non-safelisted request headers used by the Gitea Do not add arbitrary Worker preview hosts or localhost. Gitea's same-origin
backend. Do not add the production domains, the journal preview, arbitrary login page continues to use its own session cookie; Decap API calls use the
Workers preview hosts, or localhost. Gitea's same-origin login page continues OAuth bearer token.
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 After an approved Gitea deployment, verify a preflight without including a
token or cookie: token or cookie:
@@ -94,11 +94,11 @@ CORS response were verified:
- Application name: `3031023.xyz Decap CMS`. - Application name: `3031023.xyz Decap CMS`.
- Confidential client: **unchecked**. The browser client must use PKCE. - Confidential client: **unchecked**. The browser client must use PKCE.
- Redirect URI: exactly - Redirect URIs: exactly
`https://cms-3031023-xyz-www.ksloan90.workers.dev/admin/`, including the `https://cms-3031023-xyz-www.ksloan90.workers.dev/admin/` and
trailing slash. `https://3031023.xyz/admin/`, including trailing slashes.
- Do not add wildcards, localhost, either production hostname, or the journal - Do not add wildcards, localhost, the journal hostname, or branch-specific
preview as additional redirects. Worker preview aliases as additional redirects.
- Copy only the public client ID into `backend.app_id` in - Copy only the public client ID into `backend.app_id` in
`public/admin/config.yml` through a reviewed commit. `public/admin/config.yml` through a reviewed commit.
- Do not store or use a client secret in the repository, Actions, Cloudflare, - 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 | | Protection bypass allowlist | Enabled only for the named SloanOps operator user/team |
| CMS/client account | Write access, but absent from every allowlist | | 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**: Enter this exact single value in **Unprotected file patterns**:
```text ```text
@@ -197,18 +202,22 @@ Workflows, source, scripts, package files, schemas, manifests, Wrangler
configuration, `site.json` files, and the CMS application therefore cannot be configuration, `site.json` files, and the CMS application therefore cannot be
altered by choosing another branch name. 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. 1. Run `npm ci`, `npm run verify:cms`, and both site build/verifier pairs.
2. Create `cms` from the reviewed baseline only after approval. 2. For CMS application/configuration work, branch from current `cms`, open a
3. Apply both protection rules together and confirm `main` is priority 1 and pull request back into `cms`, and review the no-index version previews.
`*` is priority 2. The required CMS status is scoped to pull requests into `main`, so the local
4. Use the same non-operator account intended for Decap to prove allowed full check is mandatory before this staging merge.
Markdown edits succeed and every protected-path/direct-`main` attempt fails. 3. Open `cms` to `main` and confirm Gitea reports the exact required status
5. Open, but do not merge, a pull request into `main`. Confirm Gitea reports context above and blocks merge without its successful result and current
the exact required status context above and blocks merge without both its SloanOps approval.
successful result and a current SloanOps approval. 4. Merge deliberately, then confirm each affected production Worker workflow
6. Confirm both branch previews remain no-index and production is unchanged. 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 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 branch rules are the enforcement boundary, and the pull-request build is the
+84 -50
View File
@@ -1,19 +1,21 @@
# Decap CMS Gitea pilot and promotion # Decap CMS Gitea pilot and promotion
Status: the preview-only pilot was accepted for promotion to the permanent Status: completed and operational for internal SloanOps use as of 2026-09-04.
`cms` staging branch on 2026-09-04. Repository configuration now targets the The reviewed CMS application is deployed from `main`, Decap writes only to the
predicted stable `cms` preview origin. CORS and OAuth must be changed together protected `cms` staging branch, and the final Journal date-control repair is
only after that first preview confirms the alias. Nothing has been merged to present on both branches. Production deployments from the approved merge
`main`, and production remains unchanged. 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 Updated: 2026-09-04
## Purpose ## Purpose
Use the `3031023.xyz` demonstration repository to determine whether Record and operate the Decap CMS editing interface for both sites in the
Decap CMS can provide a useful editing interface for all sites owned under one `3031023.xyz` demonstration repository without allowing an editor to publish
root domain without allowing an editor to publish directly to production or directly to production or modify deployment code.
modify deployment code.
The pilot covers both the primary Happy Tails site and its journal subdomain. The pilot covers both the primary Happy Tails site and its journal subdomain.
That matches the SloanOps repository boundary: one private repository per root 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 owns the root-domain site also owns the hosted subdomains represented in that
repository. repository.
This plan does not authorize a production merge, domain change, contact-form The completed internal rollout does not authorize public discovery, a client
activation, repository invitation, or removal of a rollback resource. repository invitation, contact-form changes, a broader OAuth grant, or removal
of a rollback resource.
## Current evidence ## Current evidence
@@ -41,6 +44,13 @@ activation, repository invitation, or removal of a rollback resource.
writes directly to the configured branch. writes directly to the configured branch.
- The Gitea backend does not support Git LFS. Pilot media must remain small, - The Gitea backend does not support Git LFS. Pilot media must remain small,
ordinary repository assets. 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: References:
@@ -56,8 +66,8 @@ recorded in [Decap CMS pilot: Gitea settings](decap-cms-gitea-setup.md).
## Repository implementation ## Repository implementation
The reviewed pilot implementation is the baseline for the permanent `cms` The reviewed production implementation and permanent `cms` staging branch
staging branch: provide the following controls:
- `public/admin/` provides a permanently no-index admin shell and one shared - `public/admin/` provides a permanently no-index admin shell and one shared
configuration for both site builds. configuration for both site builds.
@@ -68,6 +78,10 @@ staging branch:
- All four Markdown content collections and every persisted frontmatter field - All four Markdown content collections and every persisted frontmatter field
are modeled with the current Astro/Zod bounds. Markdown controls remain are modeled with the current Astro/Zod bounds. Markdown controls remain
text-first; embedded images and general file widgets are absent. 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, - A fixed image-only Markdown record controls the primary home, About,
Services, and Contact page imagery without exposing either `site.json`. Services, and Contact page imagery without exposing either `site.json`.
- Team and page-image fields accept optional bounded uploads. Editors can add, - 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 repository cache without trusting a mutable tag or refreshing GitHub on every
job. job.
The pilot dependencies were verified on `cms-pilot`. The same protection rules The pilot dependencies were verified on `cms-pilot`, promoted to the permanent
apply to `cms`; its exact-origin CORS and OAuth values are the remaining `cms` branch, and deployed to `main`. Exact-origin CORS, PKCE OAuth, branch
operator-controlled promotion step. protection, previews, validation, and production deployment are all applied.
## Proposed pilot architecture ## Operating architecture
```text ```text
Decap at /admin/ 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 the same owner. One Decap configuration exposes clearly labeled Primary Site
and Journal collection groups. 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/admin/`
- `https://cms-3031023-xyz-www.ksloan90.workers.dev/` - `https://cms-3031023-xyz-www.ksloan90.workers.dev/`
- `https://cms-3031023-xyz-blog.ksloan90.workers.dev/` - `https://cms-3031023-xyz-blog.ksloan90.workers.dev/`
Confirm the aliases from the successful Gitea Actions run rather than treating The production admin entry is `https://3031023.xyz/admin/`. Use the stable
these predicted URLs as authoritative. `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: 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 workflows. Cloudflare credentials stay scoped to the deployment step; content
validation does not need them. validation does not need them.
Because Decap writes to a fixed `cms` branch, SloanOps creates the pull Because Decap writes to a fixed `cms` branch, SloanOps creates the pull request
request in Gitea. After an approved merge, synchronize the CMS branch with from `cms` to `main` in Gitea. Internal CMS application/configuration changes
`main` before the next editing cycle. Do not automate production merge. 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 The accepted pilot is promoted by replacing the disposable branch with the
permanent, slash-free `cms` branch. It remains a preview/staging branch and permanent, slash-free `cms` branch. It remains a preview/staging branch and
retains the same pull-request, validation, and approval path into `main`. 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 1. Edit through an approved admin origin and confirm the commit lands only on
`/admin/` redirect URI or URIs. Store only the public client ID in Decap. `cms` in an allowed content or media path.
2. Enable Gitea API CORS for the exact approved preview origin or origins. 2. Confirm the affected no-index Worker version preview succeeds.
Allow only required methods and headers, including `Authorization` and 3. Run the local CMS verifier and both site build/verifier pairs before any
`Content-Type`; do not use a wildcard origin. operator-authored CMS application or configuration PR into `cms`.
3. Create `cms` from the reviewed repository baseline. 4. Review the complete staged diff and open `cms` to `main`.
4. Review the pinned Decap admin assets beneath `public/admin/`. Do not replace 5. Require the stable secret-free validation status and SloanOps approval.
the exact version and integrity value with a floating range. 6. Merge deliberately and confirm the affected production Worker jobs pass.
5. Configure the Gitea backend for `SloanOps/3031023-xyz` and branch 7. Synchronize the resulting `main` merge commit back into protected `cms`
`cms`. before another editing cycle.
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.
## Test matrix ## 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-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. | | 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 The initial no-merge restriction applied only while the pilot was being
harmless demo-content merge requires explicit approval and repeats both public evaluated. Promotion is now allowed through the protected workflow above; each
site checks after deployment. 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; - both site families are understandable in one CMS;
- content edits preserve every modeled field and pass Astro validation; - content edits preserve every modeled field and pass Astro validation;
@@ -329,7 +363,7 @@ account control over executable deployment files.
## Rollback and cleanup ## Rollback and cleanup
If the pilot is rejected or paused: If the internal CMS is paused:
1. Revert or delete disposable changes on `cms`. 1. Revert or delete disposable changes on `cms`.
2. Delete the two disposable preview aliases/versions if desired; do not delete 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 6. Remove or retain branch protections deliberately; never weaken `main` as
cleanup. cleanup.
## Implementation estimate ## Historical implementation estimate
Expected hands-on implementation time: **6–9 hours**, normally one focused 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 working day. Budget up to two elapsed days if OAuth/CORS behavior or Gitea
+26 -20
View File
@@ -1,24 +1,30 @@
# Preview deployment and later cutover controls # Worker version previews
The preview workflow deploys static output only. It has two deliberately Non-`main` pushes build and verify the affected site, then upload a
separate Worker names: 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 | | Site key | Versioned Worker | Stable `cms` alias | Production hostname |
| --- | --- | --- | | --- | --- | --- | --- |
| `www` | `3031023-xyz-www-preview` | `3031023.xyz` | | `www` | `3031023-xyz-www` | `cms-3031023-xyz-www.ksloan90.workers.dev` | `3031023.xyz` |
| `blog` | `3031023-xyz-blog-preview` | `blog.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 The workflows derive the preview alias from the branch name, build with
contains a route, custom domain, zone ID, account ID, or token. Cloudflare preview robots rules, run output verification, and call `wrangler versions
credentials remain Gitea repository secrets. The preview workflow uses the upload`. They use the existing `CF_API_TOKEN` and `CF_ACCOUNT_ID` repository
existing `CF_API_TOKEN`, which has Account Workers Scripts edit/write for this secrets but contain no DNS, routes, custom-domain mutations, account IDs, or
pilot plus the retained Pages rollback permissions. It uses `CF_ACCOUNT_ID` as tokens in source.
the account selector. Do not grant DNS edit permissions for routine preview
deployment.
The primary Worker must pass its public preview test matrix before an operator Only the stable CMS and production admin origins are registered with Gitea
records the exact Pages deployment/DNS state and requests production-cutover OAuth/CORS. A CMS login attempt from a temporary branch alias is expected to
approval. A production deployment should use a separately named approved fail with `Unregistered Redirect URI`; do not add a wildcard. Review rendered
Worker or an explicitly promoted Worker version; it must not be an ordinary site output on the branch alias and use `https://3031023.xyz/admin/` or the
push job. Attaching `3031023.xyz`, configuring the `www.3031023.xyz` redirect, stable `cms` admin origin for editing tests.
and attaching `blog.3031023.xyz` each remain manual approval-boundary actions.
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.