Document Decap CMS demo pilot

This commit is contained in:
Kevin
2026-09-03 23:59:10 -04:00
parent 7016526d93
commit ee1010d751
2 changed files with 296 additions and 2 deletions
+7 -2
View File
@@ -3,6 +3,10 @@
This repository is a static Astro pilot for `3031023.xyz`. The existing
Cloudflare Pages site remains the rollback target while this branch is tested.
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.
## Repository layout
- `sites/www/` is the primary-site content location; it serves `3031023.xyz`.
@@ -12,8 +16,9 @@ Cloudflare Pages site remains the rollback target while this branch is tested.
- `schemas/` and `fixtures/` provide the Phase 1 JSON contract proof.
Blog posts and primary-site FAQs are now static Markdown collections with
validated frontmatter and draft filtering. CMS workflows, public sign-up
changes, n8n changes, and onboarding-worker changes remain out of scope.
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.
## Local checks
+289
View File
@@ -0,0 +1,289 @@
# Decap CMS Gitea pilot
Status: planned; no CMS, OAuth application, Gitea CORS change, branch
protection, or deployment has been enabled by this document.
Updated: 2026-09-03
## 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.
The pilot covers both the primary Happy Tails site and its journal subdomain.
That matches the SloanOps repository boundary: one private repository per root
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.
## Current evidence
- `3031023.xyz` and `blog.3031023.xyz` build independently from this
repository and deploy through separate Workers Static Assets workflows.
- A push outside `main` already uploads no-index primary and/or blog preview
versions according to the paths changed.
- Blog posts, FAQs, services, and team members are Markdown collections with
Astro/Zod validation and are suitable for a bounded Decap pilot.
- The live Gitea instance reported version `1.27.3` during the Phase 9 review.
Decap's Gitea backend requires Gitea 1.20 or newer.
- A preflight request from the proposed CMS origin currently returns `405`
without an access-control origin header. Gitea CORS must be configured before
browser-based Decap authentication or repository access will work.
- Decap authenticates to Gitea with a non-confidential PKCE OAuth application.
Its Gitea backend currently has no editorial pull-request workflow, so it
writes directly to the configured branch.
- The Gitea backend does not support Git LFS. Pilot media must remain small,
ordinary repository assets.
References:
- <https://decapcms.org/docs/gitea-backend/>
- <https://decapcms.org/docs/configure-decap-cms/>
- <https://decapcms.org/docs/widgets/image/>
- <https://docs.gitea.com/development/oauth2-provider/>
- <https://docs.gitea.com/usage/access-control/protected-branches/>
- <https://github.com/decaporg/decap-cms/issues/7115>
## Proposed pilot architecture
```text
Decap at /admin/
|
| Gitea PKCE OAuth and content/media commits only
v
cms/pilot branch
|
+--> primary no-index Worker preview
|
+--> blog no-index Worker preview
|
v
manual Gitea pull request to protected main
|
v
required validation + SloanOps approval
|
v
production merge and deploy only with separate approval
```
The shared `public/` directory may serve `/admin/` in both preview builds. That
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.
Expected preview URLs for branch `cms/pilot`:
- `https://cms-pilot-3031023-xyz-www.ksloan90.workers.dev/admin/`
- `https://cms-pilot-3031023-xyz-www.ksloan90.workers.dev/`
- `https://cms-pilot-3031023-xyz-blog.ksloan90.workers.dev/`
Confirm the aliases from the successful Gitea Actions run rather than treating
these predicted URLs as authoritative.
## Initial Decap scope
Allow editing and creation only within these collections:
| CMS group | Repository path | Initial capability |
| --- | --- | --- |
| Primary site FAQs | `sites/www/content/faq/**` | Create, edit, draft, reorder, delete after confirmation |
| Primary services | `sites/www/content/services/**` | Create, edit, draft, reorder, feature |
| Primary team | `sites/www/content/team/**` | Create, edit, draft, reorder, select an approved image |
| Journal posts | `sites/blog/content/posts/**` | Create, edit, draft, date, tag, update |
| Shared pilot media | `public/images/uploads/**` | Upload bounded raster images after text editing is proven |
Do not expose `sites.json`, either `site.json`, schemas, workflows, Wrangler
configuration, package files, source components, scripts, contact routing, or
analytics through the CMS during this pilot.
Configure the default media library with a conservative file-size limit. Do
not accept executables, archives, HTML, scripts, or SVG uploads. Asset rights,
alt text, crop, dimensions, and optimization remain operator review items even
when the CMS accepts an upload.
## Repository protection model
### `main`
- Disable direct pushes for CMS/client users.
- Require a pull request.
- Require at least one SloanOps approval.
- Require the dedicated CMS content validation status.
- Block merging an outdated or rejected pull request.
- Permit merges only by the SloanOps operator allowlist.
- Keep force-push disabled.
### Fallback rule for non-production branches
Create a branch-protection fallback covering every branch, with the specific
`main` rule at higher priority. CMS/client users may push only the following
unprotected patterns:
```text
sites/www/content/**
sites/blog/content/**
public/images/uploads/*.jpg
public/images/uploads/*.jpeg
public/images/uploads/*.png
public/images/uploads/*.webp
public/images/uploads/*.avif
```
Add the equivalent nested-path globs if the CMS is allowed to create media
subdirectories. Listing raster extensions explicitly prevents an editor from
using the media directory as a general unprotected file path.
All other paths remain protected, including:
```text
.gitea/**
astro.config.mjs
package.json
package-lock.json
schemas/**
scripts/**
src/**
wrangler/**
sites.json
sites/*/site.json
public/admin/**
```
SloanOps operator accounts may use the branch-rule bypass allowlist for
reviewed development. Verify the restrictions through the same Gitea account
used by Decap; a UI-only field restriction is not a repository security
boundary.
The protected-path fallback matters even though `main` requires a pull
request. Preview workflows inherit Cloudflare credentials. An editor must not
be able to create another branch, alter a workflow or executable build file,
and run unreviewed code with those credentials.
## Pull-request validation
Add a secret-free validation workflow for pull requests into `main`. It should
always:
1. install locked dependencies;
2. build and verify the primary site;
3. build and verify the blog;
4. reject content that fails the Astro/Zod schema; and
5. report one stable required status context.
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/pilot` 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.
## Gitea and Decap setup
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/pilot` from the reviewed repository baseline.
4. Add pinned Decap admin assets beneath `public/admin/`. Do not load a floating
major-version range from a CDN.
5. Configure the Gitea backend for `SloanOps/3031023-xyz` and branch
`cms/pilot`.
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
Record the Gitea execution, commit, and preview identifiers for each test. Do
not record OAuth tokens or Cloudflare credentials.
| ID | Test | Expected result |
| --- | --- | --- |
| DC-01 | Build both sites before CMS changes | Current build and verifier suites pass. |
| DC-02 | Open each preview `/admin/` route | Admin shell loads and remains no-index. |
| DC-03 | Authenticate with the approved operator account | PKCE login succeeds and the configured collections load. |
| DC-04 | Authenticate with an account without repository write access | Repository editing is unavailable. |
| DC-05 | Edit an existing draft journal post | One content-only commit lands on `cms/pilot`; the blog preview rebuilds. |
| DC-06 | Create, edit, and delete a disposable journal post | Filename, dates, tags, body, draft state, and cleanup remain schema-valid. |
| DC-07 | Edit a primary FAQ | Only the primary preview rebuilds and displays the change. |
| DC-08 | Edit service ordering and featured state | YAML types remain correct and the primary verifier passes. |
| DC-09 | Edit a team entry without changing its image | Existing fields and image path are preserved exactly. |
| DC-10 | Upload a bounded raster image | File lands only in the approved media path and renders on the relevant preview. |
| DC-11 | Attempt an oversized upload and a disallowed file type | The CMS size limit rejects the oversized file before upload; Gitea's unprotected-extension patterns reject the disallowed path. |
| DC-12 | Attempt to modify `.gitea/workflows/**`, source, package, script, schema, Wrangler, and site-manifest paths with the CMS account | Gitea rejects every push. |
| DC-13 | Attempt to create a differently named branch and change a protected file | The fallback rule rejects the push; branch naming cannot bypass protection. |
| DC-14 | Attempt a direct push to `main` | Gitea rejects it. |
| DC-15 | Open a pull request from `cms/pilot` to `main` | Both sites validate; required review and merge controls are present. |
| DC-16 | Attempt to merge without status or approval | Gitea blocks the merge. |
| DC-17 | Inspect both preview responses | Expected routes work; preview metadata/headers prevent indexing; production remains unchanged. |
| DC-18 | Revert the disposable CMS changes on `cms/pilot` | Preview returns to baseline without changing either public site. |
| 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.
## Acceptance criteria
Accept the pilot only when:
- both site families are understandable in one CMS;
- content edits preserve every modeled field and pass Astro validation;
- previews update reliably without moving production traffic;
- direct `main` pushes and protected-path changes fail for the CMS account;
- PR validation and operator approval are mandatory;
- media limits and repository growth remain acceptable;
- login/session behavior is usable; and
- rollback requires no production-domain or DNS change.
Stop and redesign if the Gitea backend cannot honor the branch/file
protections, OAuth grants materially broader access than the accepted account
role, content serialization loses fields, or previews require granting the CMS
account control over executable deployment files.
## Rollback and cleanup
If the pilot is rejected or paused:
1. Revert or delete disposable changes on `cms/pilot`.
2. Delete the two disposable preview aliases/versions if desired; do not delete
production Workers or retained legacy rollback resources.
3. Revoke/delete the Decap OAuth application.
4. Remove the pilot CORS origins and redeploy Gitea after verifying normal Git,
UI, API, and Actions behavior.
5. Remove `/admin/` only through a reviewed commit.
6. Remove or retain branch protections deliberately; never weaken `main` as
cleanup.
## 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
protection semantics require troubleshooting.
| Work | Estimate |
| --- | ---: |
| Decap admin shell, pinned dependency, and Gitea configuration | 1–1.5 hours |
| Model blog, FAQ, services, and team collections | 1.5–2 hours |
| Add secret-free PR validation and preview links/instructions | 1–1.5 hours |
| Configure CORS, OAuth, CMS branch, and protection rules | 1–1.5 hours |
| Run the authentication, editing, protection, preview, and rollback matrix | 1.5–2.5 hours |
| Record results and resolve small integration defects | 0.5 hour |
This estimate covers the internal demo pilot, not a polished client-editable
service. Client rollout still needs observed usability, account invitation and
offboarding procedures, support scope, media policy, and pricing.