feat(evergreen-tracks): daily 06:00 ET cron for standard/trailing, gate kept as break-glass - #36902
feat(evergreen-tracks): daily 06:00 ET cron for standard/trailing, gate kept as break-glass#36902sfreudenthaler wants to merge 4 commits into
Conversation
…te kept as break-glass Promotion is now end-to-end unattended: a daily schedule runs plan -> apply with no human in the loop. Manual dispatch keeps the plan -> gate -> apply flow behind the evergreen-tracks-apply required-reviewer environment as the break-glass path (review a plan before it lands, or move tags off-cycle). - gate is dispatch-only (`if: github.event_name == 'workflow_dispatch'`); apply accepts a SKIPPED gate but not a failed/rejected one, hence explicit result checks rather than a bare always(). - Restore input fallbacks in both jobs: a schedule event carries no inputs, so github.event.inputs.* is empty on the cron path (would have promoted with empty --repo/--standard-days). - Drift check and its messages generalized from "approved" to "planned" — it now guards both paths. - Still scoped --tracks standard,trailing; latest remains the release pipeline's. Safe because moving a floating tag deploys nothing: every customer manifest pins an immutable <version>@sha256:<digest>, and the in-cluster reconciler (armed on all four prod clusters as of #8086/#8112) resolves the track tag only at run time inside its biweekly on-parity Wednesday window. Verified no continuous watcher exists (no Argo Image Updater/Keel, no floating-tag refs, no imagePullPolicy: Always on customer pods). Daily promotion also isn't daily tag movement — the planner is forward-only and age-gated (14d/28d). Verified: all six plan/gate result combinations resolve correctly (cron applies, rejected/failed gate does not), 66 tests green. Closes: #36705 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L6UZqGwpjPQrnYyZfYAxiq
|
Claude finished @sfreudenthaler's task in 6m 35s —— View job PR Review — evergreen-tracks daily cron
I traced the workflow against the engine ( One non-blocking finding: New Issues
Notes (not blocking)
Everything else — the six plan/gate combinations, the skipped-gate accept, the rejection-stays-silent path, the |
…track move The daily cron is unattended, so a silent failure would mean tracks quietly stop advancing with nobody the wiser. Adds a notify job using the existing core-cicd/notification/notify-slack action, posting to #dot-releases (CE1TBQU00). Deliberately quiet: failures post, actual track moves post, and the common "no track moves needed" day posts nothing. A rejected break-glass approval leaves apply SKIPPED rather than failed, so declining a plan never pages the channel. Both steps are continue-on-error — Slack being down never fails a promotion. The apply job now emits moved/summary outputs; the summary join uses `paste -sd ';' | sed` because paste -d takes a cycling char list, not a string. Verified: summary extraction across both-moved / one-moved / no-moves / held-only plans; YAML parses; 66 tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L6UZqGwpjPQrnYyZfYAxiq
Added: #dot-releases notifications (commit 4f584fe)Wired up using the repo's existing Designed to stay quiet, since a releases channel shouldn't get a daily "nothing to do" ping:
Two details worth calling out:
Verified the summary extraction against both-moved / one-moved / no-moves / held-track-only plans. YAML parses, 66 tests still green. |
|
✅ Slack verified after the
|
…ck reconciles
Two review findings, both confirmed against the engine:
1. The failure alert claimed tags were "unchanged, not half-moved". False:
cmd_promote applies moves in a per-track loop and point_tag runs imagetools
with check=True, so standard succeeding then trailing failing leaves a real
half-moved state — the alert asserted the opposite of what happened. Now
points responders at the run log for which tracks actually applied.
2. The Slack summary grep ('^(standard|trailing) -> ') missed the held-track
reconcile line ("standard (held) -> reconcile to standard_hold"). The engine
physically re-points that tag, so an unattended cron could move a real tag and
post nothing. Widened with an optional '(held) ' group.
Headline generalized "advanced" -> "tags moved", since a held reconcile is a
correction rather than an advance.
Verified across five plan shapes: both-advanced, quiet day, held-reconcile-only
(previously silent), held+advance, and held-but-consistent (correctly stays
quiet — no tag is touched in that case). 66 tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6UZqGwpjPQrnYyZfYAxiq
Both Medium findings confirmed and fixed (commit 434251d)Checked each against the engine rather than taking them on faith — both were real. 🟡 "not half-moved" was a false guarantee. Confirmed: 🟡 Held-track reconciles were silently dropped. Confirmed: the engine logs Headline generalized Verified across five plan shapes:
That last row matters: when a held track already matches its marker the engine logs 66 tests green; YAML parses. The DST note the reviewer flagged is already documented inline and in the README. |
|
semgrep doens't like the mutable tag but it's acceptable here because we want to pull the latest action of that major. minors and patch updates in rapidly devloping tech are worth the security boost we get from auto-patching on a floating major tag. |
…Slack alert Review asked whether a gate-job infrastructure failure should page #dot-releases. Answer: no, and the suggested condition would be harmful. Verified empirically — rejecting a deployment review marks gate=failure, apply=skipped, run=failure, so a rejection and a gate infra failure are indistinguishable from needs.gate.result. Adding `|| needs.gate.result == 'failure'` would therefore page the channel on every deliberate rejection. The only uncovered case is a gate infra failure, which cannot occur on the unattended cron (gate is dispatch-only) and on the manual path leaves a red run in front of the operator who just triggered it. Comment only — no logic change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L6UZqGwpjPQrnYyZfYAxiq
🟡 Gate-failure alert: won't fix — the suggested condition would be harmful (commit 131bad7)The finding's own "what to verify" was the right question, so I tested it rather than reasoned about it. Dispatched a run and rejected the deployment (run 31065188863):
So the reviewer's stated assumption is wrong in the decisive way: GitHub reports a reviewer rejection as Adding And that residual case is thin:
No logic change. Added a comment recording the verified behavior and the reasoning, so the next reader doesn't "fix" it back into a noise source. Bonus: this also empirically confirms a claim this PR has been making without proof — a deliberate "no" never pages the channel. Verified: both notify steps skipped, and no message appeared in #dot-releases. |
|
Tick the box to add this pull request to the merge queue (same as
|
What
standard/trailingpromotion is now end-to-end unattended: a daily cron at 06:00 ET runsplan→applywith no human in the loop. Per #36705 this is Option B, and it's surgical — the plan/apply structure, drift check, isolation test, and docs from #36652 all stay.Manual dispatch is retained as the break-glass path:
plan→gate→apply, wheregatestill waits on theevergreen-tracks-applyrequired-reviewer environment. Use it to review a plan before it lands, or to move tags off-cycle.latestis untouched — the release pipeline moves it on every GA cut.How the two paths diverge
schedule10:00 UTCif: github.event_name == 'workflow_dispatch')workflow_dispatchapplyaccepts a skipped gate but not a failed or rejected one — hence explicit result checks rather than a barealways().Why unattended daily promotion is safe
Moving a floating tag deploys nothing. Verified against
dotCMS/infrastructure-as-codeat HEAD:<version>@sha256:<digest>; zero floating-tag references exist anywhere in the repo.timeZone: America/New_York). A tag moved on a Tuesday has no effect until that window.imagePullPolicy: Alwayson customer pods.Also: daily promotion ≠ daily tag movement. The planner is forward-only and age-gated (14d / 28d), so a track moves only on the day a release actually crosses its threshold.
Bug this would have shipped without
A
scheduleevent carries no inputs, sogithub.event.inputs.*is empty on the cron path — #36652 had removed the fallbacks when it dropped the old cron. Restored in both jobs; without them the nightly run would have invoked the engine with an empty--repoand empty day thresholds.Verification
plan/gateresult combinations resolve correctly — cron applies; rejected, cancelled, or failed-plan paths do not.dotcms/dotcms-test(run 31037723308): gate waited → approved →plan unchanged; applying→ tag moved. Plan correctly excludedlatest.Docs
README cadence section rewritten (daily cron + break-glass + the safety rationale); RUNBOOK intro and quick-reference updated — routine promotion no longer needs an operator, so the taint/hold controls are framed as the intervention levers.
Closes: #36705
🤖 Generated with Claude Code