Skip to content

Guard content PRs: fail validate-content when CMS-managed paths change outside Decap - #150

Merged
Adam-S-Daniel merged 1 commit into
mainfrom
claude/funny-pascal-uwixq4
Jul 10, 2026
Merged

Guard content PRs: fail validate-content when CMS-managed paths change outside Decap#150
Adam-S-Daniel merged 1 commit into
mainfrom
claude/funny-pascal-uwixq4

Conversation

@Adam-S-Daniel

@Adam-S-Daniel Adam-S-Daniel commented Jul 10, 2026

Copy link
Copy Markdown
Owner

What

Consumer sites manage content through Decap's editorial workflow (one entry per PR on a cms/<collection>/<slug> branch), but nothing enforced that — label-non-decap-prs.yml only tags offenders, so a hand-made PR editing _posts/ merges as easily as a CMS one (context: today's Adam-S-Daniel/adamdaniel.ai#2549 → #2553 rework).

This adds a content PR conformance guard that fails the already-required editorial / validate-content context when a PR touches CMS-managed content paths without being Decap-created — no new required check, no ruleset change, no cms-automerge-nudge required_contexts churn; consumers adopt it through an ordinary platform version bump.

  • scripts/content-pr-guard.js (new) — pure decision logic, no deps/network/fs. Content paths = base collections' folders + media_folder (from theme/admin/config.base.yml) + the site seam's (admin/collections.site.yml) folder: values (so jodidaniel.com's per-section collections are covered) + optional extras. "Decap-shaped" = the same triad label-non-decap-prs.yml uses (branch prefix sourced from e2e/cms-fixture-pr.js's FIXTURE_BRANCH_PREFIX, body marker, decap-cms/* label) so the guard, labeller, and fixture harness cannot drift.
  • cms-editorial-workflow.yml — new optional platform_repo/platform_ref inputs (same pin-to-uses convention as label-non-decap-prs.yml); validate-content gains a platform-module checkout + a fail-fast guard step (before Ruby setup). On violation it upserts a marker PR comment explaining which files are CMS-managed, why the restriction exists, how to redo the change via the site's /admin/ (URL derived from the site's _config.yml, never hardcoded), and the escape hatch. Comment/label API work is isolated in try/catch so an API hiccup can never flip the verdict. The job keeps no concurrency block (#1815).
  • Escape hatch — a maintainer applies the content-guard/override label (auto-created; name sourced from the module and deliberately absent from the workflow text, lint-enforced) for deliberate maintenance (fixture repair, bulk migration); the labeled event re-runs the check, which then passes.
  • e2e/content-pr-guard.test.js (new, pure-fs lane) — 18 tests: the decision matrix (incl. seam-derived dirs, media uploads, file-list capping) + wiring lints (inputs/defaults, platform checkout, pin byte-equality with the rest of the file, the #1815 no-concurrency invariant, example-caller pin parity). Registered in PLATFORM_META_SPECS per the feat(admin): ship admin/ via the theme gem (Option 1A, issue #5) #16 recurrence guard.
  • examples/site caller — passes platform_ref matching its uses: pin.

Sanctioned flows unaffected

Decap entry/delete-recovery PRs (cms/* branches), e2e fixture PRs (cms/e2e-fixture/*), dependabot/platform-bump/skills-sync/tool-sync PRs (no content paths) all pass. One observation, deliberately not addressed here: publish-scheduled-posts.yml pushes content commits directly to main with the default token, while the checked-in adamdaniel.ai ruleset shows a pull_request rule and empty bypass_actors — those two mechanisms look mutually inconsistent regardless of this PR (worth a look).

Test plan

  • Full self-CI pure-fs lint suite (77 spec files, deny-filtered, --project=chromium-light): 861 passed, incl. the 18 new tests
  • actionlint-relevant style checks ride the existing lints (run-name, pin comments); YAML parses clean
  • Pre-existing failures only: 3 in field-library-ref-render.test.js (Ruby US-ASCII locale issue in this container; reproduces identically on unmodified main via git stash — untouched here)

Rollout

  1. Merge → cut a release (gh workflow run release.yml -f version=v0.1.59+).
  2. Consumers pick it up via the normal caller pin bump; when bumping, also add with: platform_ref: <same tag> to the editorial caller (defaults to main otherwise — works, but can version-skew).
  3. No ruleset changes needed anywhere.

🤖 Generated with Claude Code

https://claude.ai/code/session_01D7rqKwvKrn92eNE5LgCvBq

…e Decap

Content edits that bypass /admin skip the editorial workflow's draft/review
status, stable per-entry preview alias, and publish/schedule semantics, and
can collide with Decap's on-repo state — and nothing enforced the contract
(label-non-decap-prs.yml only tags offenders). Ride the already-required
`editorial / validate-content` context instead of adding a new required
check: a new pure-logic scripts/content-pr-guard.js (unit-testable, sourcing
the cms/ branch convention from e2e/cms-fixture-pr.js so the guard, labeller,
and fixture harness can't drift) fails the job with an explanatory PR comment
when a non-Decap PR touches base-collection folders, uploaded media, or the
site seam's collection folders. Escape hatch: a maintainer applies the
override label (name sourced from the module, deliberately not re-typed in
the workflow) and the labeled event re-runs the check. Comment/label API
work is isolated so an API hiccup can never flip the verdict; the step runs
before Ruby setup to fail fast; validate-content keeps NO concurrency block
(#1815). Callers should pin the new platform_ref input to their uses: pin.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D7rqKwvKrn92eNE5LgCvBq
@Adam-S-Daniel
Adam-S-Daniel merged commit 9a4ee60 into main Jul 10, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants