feat(cicd): 36605 changelog auto-publish to dev site #36606
feat(cicd): 36605 changelog auto-publish to dev site #36606sfreudenthaler wants to merge 24 commits into
Conversation
Speckit spec + quality checklist for auto-publishing release changelogs to the dev site on release publish. Spec follows the dotCMS spec template shape proposed in #36416 (plain markdown; no scaffolding dependency). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ogYXVeZsBVUHkwzdStYPJ
The promotion engine landed in a new top-level cicd/ folder (#36161); CI support code belongs with the rest of the CI machinery. Pure move + path updates (3 workflows, README); 64 tests pass from the new home. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ogYXVeZsBVUHkwzdStYPJ
|
Claude finished @sfreudenthaler's task in 6m 7s —— View job Claude finished @sfreudenthaler's task in 5m 39s —— View job PR Review — changelog auto-publish to dev siteThis is a well-structured, well-tested addition. The design is sound: dry-run default, non-blocking in the release pipeline, per-version concurrency serialization, human-edit protection via immutable New Issues
Notes (non-blocking, no action needed)
No blocking issues. |
Its only caller (maven-release-process.yml) dropped the invocation in #27667 and the workflow itself is gone; no references in repo or org. Removes the now-empty top-level cicd/ folder. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ogYXVeZsBVUHkwzdStYPJ
…human-edit protection, Slack alerts, descope LTS/CLI Per review: patch-of-older-version gets own entry with actual date (FR-013); runs never auto-backfill (FR-012); entries last modified by a human are never overwritten without explicit override (FR-011, SC-006); failures and skips notify #dot-releases (FR-008, US3); LTS and CLI releases descoped v1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ogYXVeZsBVUHkwzdStYPJ
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ogYXVeZsBVUHkwzdStYPJ
Adds the speckit PLAN artifacts (plan.md, research.md, data-model.md) for the automated release-changelog publishing feature. Architecture: generation reuses the existing deterministic gather-release-data source via a new site-format prompt template; delivery is a new changelog-publisher Python CLI (mirroring the evergreen-tracks house pattern) doing idempotent upsert-by-version + System Workflow Publish with disabledWYSIWYG, human-edit protection, and Slack-on-failure to #dot-releases. Wired as a non-blocking phase gated on release-prepare is_latest (reuses the existing CLI/LTS exclusion). Constitution Check passes; ADR gate ran (no accepted-ADR conflicts). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ogYXVeZsBVUHkwzdStYPJ
Dependency-ordered tasks.md generated from the approved plan: Setup → Foundational (resolves the 3 open questions + exit-code contract) → US1 auto-publish (MVP) → US2 idempotency/human-edit protection → US3 Slack → Polish. Every user story encodes the Constitution V TDD gates (tests → approval GATE → Red GATE → implementation). The JUnit/Postman/Karate/e2e "cannot implement" declaration is recorded as signed off (CI-only tooling), not re-asked. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ogYXVeZsBVUHkwzdStYPJ
…n questions (#36605) Phase 1 (setup) + Phase 2 (foundational) for automated changelog site publishing: - T001/T003: scaffold changelog-publisher Python package mirroring evergreen-tracks (pyproject with console_script, src/ layout, tests/fixtures, local .gitignore). - T002: uv env + pytest + responses install and collect (empty suite → pytest's exit-5 "no tests collected" sentinel; goes green once US1 tests land). - T004: verified live corpsites Dotcmsbuilds field values (26.06.30-01) into data-model.md — current-track lts=3, released=true, download=1, showInChangeLog=true; modUserName = authenticating user's display name. - T005: exit-code/stdout contract in README (0=success/skip, skip marker ::changelog-skip::, 2=usage, 1=runtime failure). - T006: golden-file lives in gather-release-data jest suite (recorded in README + research.md D2). - T007: client.py skeleton — token read-once from DOTCMS_DEVSITE_TOKEN, Bearer session, PUBLISH_ACTION_ID, _search/fire seams (never logs the token). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ogYXVeZsBVUHkwzdStYPJ
TDD Red for User Story 1 (auto-publish happy path + generation format): - T010 test_client.py: _search 0-hit/1-hit parsing (query string, limit 2, entity.jsonObjectView.contentlets), Bearer header. - T011 test_publisher.py: create-path fire payload shape + disabledWYSIWYG regression guard (FR-005), no identifier on create. - T012 test_cli.py: version.py current-track validation; _lts_/CLI forms rejected before any network call (FR-007). - T013 test_cli.py: dry-run-by-default vs --apply (no fire in dry-run). - T014 gather-release-data jest: site editorial format golden guard + asserts prompt-template-site.md exists/encodes the format. Importable NotImplementedError skeletons for version/publisher/cli so Red is behavioral (14 pytest failures, all NotImplementedError; jest fails only on the missing prompt-template-site.md) rather than import/collection errors. No US1 implementation yet — awaiting approval + Red gate (T015/T016). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ogYXVeZsBVUHkwzdStYPJ
US2 (P2) implementation — all 19 pytest tests green (14 US1 + 5 US2): - T031 publisher decision table: 0 hits -> create; 1 hit owned by service account -> update in place with the found identifier; 1 hit + other modifier + no --force -> skip with a reason naming the human; 1 hit + --force -> update; >1 hits -> AmbiguousMatchError. Ownership keys on the immutable modUser id, falling back to modUserName only when the entry carries no id (per lead directive; documented in data-model.md). - T032 cli.py: --force (manual operator override; never passed by the release workflow) and --service-account (or env DOTCMS_DEVSITE_SERVICE_ACCOUNT); AmbiguousMatchError -> exit 1; skip -> ::changelog-skip:: marker + exit 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ogYXVeZsBVUHkwzdStYPJ
TDD tests for User Story 3 (failures/skips announced, never silent): - T033 failure exit contract: 401 (auth) and 4xx (payload rejected) each -> non-zero exit; the bearer token never appears in stdout/stderr/logs (Constitution III); no skip marker. - T034 skip-vs-failure distinction: protective skip -> exit 0 + ::changelog-skip:: marker; real failure (incl. >1-hit AmbiguousMatchError) -> non-zero exit, no marker. Note: these pass on arrival — the exit/stdout contract they assert was necessarily authored in cli.py under T020 (US1) and T032 (US2), per the T005 contract. There is no separate Red to stage for the unit-testable slice; the genuinely new US3 implementation is the workflow Slack branch (T038/T039, YAML). Flagged to the lead at the US3 gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ogYXVeZsBVUHkwzdStYPJ
) US3 (P3) workflow wiring — failures/skips announced in #dot-releases, never silent, never blocking the release (FR-008, SC-005): - T038 channel id: #dot-releases = CE1TBQU00 (looked up by lead), as the default on a new slack_channel_id phase input (override-able; no repo variable / unset-var failure mode). - T039 Slack branch in the phase: the Publish step captures the tool's exit code + stdout and sets result=success|skip|failure per the T005 contract; failure -> 🚨 wording, protective skip ->⚠️ wording (distinct), success -> no post. The step always exits 0 and the job is allow_failure, so a changelog hiccup never fails the release. SLACK_BOT_TOKEN threaded through cicd_6-release.yml; service-account id via vars.DOTCMS_DEVSITE_SERVICE_ACCOUNT (ops-provisioned, non-secret). - T037/T040: CLI exit/stdout contract confirmed; release stays green (finalize/report do not depend on this job). T036 Red ruled satisfied-by-declaration (rationale in tasks.md). Suites green: 23 pytest + 6 jest; both workflow YAMLs valid. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ogYXVeZsBVUHkwzdStYPJ
- T041 README: argument table, --force operator override, exit/skip contract, explicit per-version manual-backfill procedure (no auto-backfill, FR-012), and the one-time manual end-to-end validation checklist. - T042 edge cases (test_edge_cases.py): internal-maintenance-only note still publishes an entry; same-day hotfix -02/-03 each create their own row; older-line -04 patch carries today's date and searches/writes only its own version (FR-013). - T043: full suites green — 26 pytest + 38 jest (whole gather-release-data suite). - T044 security/hygiene: token never logged/echoed; GITHUB_OUTPUT carries only result/reason (never the token); version validated before any network call; .venv and caches gitignored and untracked. All 44 tasks complete. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ogYXVeZsBVUHkwzdStYPJ
…guard, token error handling (#36605) Verifier fixes (same branch, core behavior already PASS): 1. (MUST/SC-005) Add a trailing `if: failure()` catch-all Slack step so a failure in any pre-publisher step (npm ci, tsc, gather-release-data, Claude generation, verify-notes) — which would otherwise skip the result-gated Slack steps while allow_failure keeps the run green — still posts a failure notice to #dot-releases. The two specific steps are unchanged; no double-post (the publisher step exits 0, so success/skip aren't a job-failure state). 2. (SHOULD) verify-notes uses `[ -s ]` not `[ -f ]` — an empty notes file no longer publishes an empty releaseNotes. 3. (SHOULD) cli.py catches RuntimeError (missing DOTCMS_DEVSITE_TOKEN) -> clean one-line error, rc 1, no traceback; new pytest asserts rc=1 / no traceback / no network call. 4. (CHEAP) Docker-tag match tightened to `dotcms/dotcms:*_*` (sha-tagged version_sha form) so it can't match `:latest`; sha-less fallback kept; emit-order assumption noted inline. Suites green: 27 pytest + 38 jest; both workflow YAMLs valid. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ogYXVeZsBVUHkwzdStYPJ
…cal working artifacts Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ogYXVeZsBVUHkwzdStYPJ
…ADME Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ogYXVeZsBVUHkwzdStYPJ
…e duplicate test helpers (#36605) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ogYXVeZsBVUHkwzdStYPJ
One step, one channel: the if covers publisher failure, protective skip, and pre-publisher failure(); wording switches inline and the reason falls back for pre-publisher errors. Behavior per FR-008/SC-005 unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ogYXVeZsBVUHkwzdStYPJ
…ersion concurrency group (#36605) Claude review finding: dotCMS _search is ES-backed/async-indexed, so a fast re-run after a create could miss the new row and double-create (then every run hits AmbiguousMatchError). The tool now polls until the created row is searchable before exiting (sequential window), and the workflow job gets a per-version concurrency group (concurrent window) — spec edge case satisfied. Counter-regex finding rejected: mirrors release-prepare's own is_latest gate, so an out-of-range version never reaches this job. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ogYXVeZsBVUHkwzdStYPJ
…om/dotCMS/core into issue-36605-changelog-site-publish
|
RE: the two medium findings
|
… site content (#36605) Retires the second Claude call and the site prompt template entirely. The ai-release-notes template becomes the one shared editorial format (site style: prose intro, no emoji, site section set, double-bracket issue links, no anchors); the site phase now just fetches the published release body via gh release view and hands it to the publisher, which injects per-version heading anchors and strips GitHub's compare footer mechanically (tested, idempotent). Removes node/tsc/gather/claude-code-action/verify steps, the ANTHROPIC_API_KEY secret, and the OIDC permission from the phase — the site path no longer depends on inference at publish time, and a hand-edited release re-syncs the site on re-run. Suites: 30 pytest + 32 jest green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ogYXVeZsBVUHkwzdStYPJ
| import requests | ||
|
|
||
| # corpsites-headless is the site's backend; overridable for local testing only. | ||
| BASE_URL = os.environ.get("DOTCMS_DEVSITE_URL", "https://corpsites-headless.dotcms.cloud") |
There was a problem hiding this comment.
do we want to expose the corpsites URL as a default value in our public repo?
What
End-to-end implementation of automated release changelog publishing to dev.dotcms.com, spec-first (speckit flow: spec → plan → tasks → TDD implementation → adversarial verification), plus two CI-hygiene cleanups. All pieces are independently mergeable — no dependency on #36416 or any other open PR.
1. Spec (
specs/36605-changelog-site-publish/)Spec (13 FRs incl. human-edit protection, no-auto-backfill, older-version patch semantics; LTS/CLI descoped v1) plus
data-model.md(live-verifiedDotcmsbuildsfield contract). The working artifacts (plan/tasks/research/checklists — incl. Constitution Check and ADR-gate consultation, 3 relevant ADRs, none conflicting; 44/44 TDD tasks complete) were used during development and deliberately not committed.2. Implementation
.github/actions/core-cicd/changelog-publisher/— new Python CLI (mirrors the evergreen-tracks package pattern): searchesDotcmsbuildsby version (minor_dotraw), upserts create/update-in-place, fires the System Workflow Publish action withdisabledWYSIWYG:["releaseNotes"](markdown-preservation guard). Dry-run by default;--applyto fire;--force(manual only) to override human-edit protection, which skips any entry last modified by a non-service-account user (immutablemodUserid comparison, display-name fallback). Exit contract: 0 = success/protective-skip (::changelog-skip::marker), 2 = validation (out-of-scope version rejected before any network call), 1 = runtime failure. 27 pytest (responses-mocked) green.gather-release-data/prompt-template.mdbecomes the one shared editorial template (site style: prose intro, no emoji, site section set,[[#N](issues url)]links), the GitHub release body is the single shared artifact, and the publisher injects the site's per-version heading anchors mechanically at publish time. One inference per release; the site path has zero Claude dependency at publish time; a hand-edited GitHub release re-syncs the site on re-run. Note: this restyles the GitHub releases page to the site's editorial format (deliberate)..github/workflows/cicd_comp_changelog-site-publish-phase.yml+ additive job incicd_6-release.yml— runs afterrelease-notes(fetches the published release body viagh release view), gated onis_latest(current-track GA only: excludes CLI/LTS), non-blocking (continue-on-error; a publish failure can never fail a release), serialized per version via a concurrency group. Slack notices to#dot-releases(CE1TBQU00) on failure, protective skip, and pre-publisher step failure (catch-allif: failure()).3. CI hygiene (independent commits)
evergreen-tracksuv package from top-levelcicd/into.github/actions/core-cicd/(64 tests pass from new home).cicd/resources/update_osgi_plugins.py(caller removed Feb 2024 in #27655: release fixes for lts #27667; no consumer repo- or org-wide) — the top-levelcicd/folder is gone.Operational prerequisites before first live run (not code)
DOTCMS_DEVSITE_TOKEN.DOTCMS_DEVSITE_SERVICE_ACCOUNTto that account'smodUserid (unset = all updates skip protectively).SLACK_BOT_TOKENmust be available to the release workflow (existing secret, new consumer).#dot-releasesand the changelog page — there is no non-prod corpsites, so the maiden--applyis the real end-to-end test (manual dry-run checklist in the package README).Verification
Adversarially reviewed by an independent pass: FR conformance in code, token-leak grep, workflow needs/if semantics vs the real release pipeline, docker-tag emit-order trace, and date-format acceptance traced through
ContentletAPI.DEFAULT_DATE_FORMATS. All findings fixed (5bd97cac13).Closes: #36605 (feature issue, under epic #35693)
🤖 Generated with Claude Code
https://claude.ai/code/session_012ogYXVeZsBVUHkwzdStYPJ