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.