From ee1010d7519e4780040b1489d0a586b7f882d621 Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 3 Sep 2026 23:59:10 -0400 Subject: [PATCH] Document Decap CMS demo pilot --- README.md | 9 +- docs/decap-cms-pilot.md | 289 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 296 insertions(+), 2 deletions(-) create mode 100644 docs/decap-cms-pilot.md diff --git a/README.md b/README.md index 2e68f6c..25ab799 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/decap-cms-pilot.md b/docs/decap-cms-pilot.md new file mode 100644 index 0000000..80bb92f --- /dev/null +++ b/docs/decap-cms-pilot.md @@ -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: + +- +- +- +- +- +- + +## 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.