Skip to content

feat(api): report promotion window state on Stage status - #6772

Merged
hairyhum merged 5 commits into
mainfrom
EronWright/promotion-schedule-status
Aug 10, 2026
Merged

feat(api): report promotion window state on Stage status#6772
hairyhum merged 5 commits into
mainfrom
EronWright/promotion-schedule-status

Conversation

@hairyhum

@hairyhum hairyhum commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Description

Supersedes #6737

Some name replacements compared to #6737:

  • promotionSchedule -> promotionWindowStatus - unify naming with promotionWindows
  • closed is non-optional if promotionWindowStatus exists for clarity
  • reason should only describe a reason for closed window, nextCloseReason is added to describe a reason for nextClose - this is to prevent people from thinking that reason describes a reason why it's open.

Adds PromotionWindowStatus to StageStatus, reporting whether promotion windows (#6710) currently permit promotion of a Stage.

Variations a client will observe

No windows gate the Stage. The common case — the field is absent entirely, not an empty object.

status:
  autoPromotionEnabled: true
  # no promotionSchedule key

Frozen, reopening known.

status:
  promotionSchedule:
    closed: true
    reason: 'Promotion freeze "change-freeze" is active'
    nextOpen: "2026-07-30T22:00:00Z"

Frozen, no reopening known. nextOpen is omitted. Here a one-shot Allow window has elapsed, so promotion is forbidden for good; an implementation may also report an indefinite freeze this way.

status:
  promotionSchedule:
    closed: true
    reason: No promotion window is currently active

Promotion allowed, freeze approaching. closed is false. nextCloseReason describes the freeze that is coming, so a warning can name it.

status:
  promotionSchedule:
    closed: false
    nextClose: "2026-07-31T09:00:00Z"
    nextCloseReason: 'Promotion freeze "change-freeze" begins'

Promotion allowed, nothing scheduled ahead. A one-shot Deny window that has elapsed leaves no boundary to report. This is identical to promotionSchedule field being absent

status:
  promotionSchedule:
    closed: false

Contract

  • closed indicates whether promotions are blocked
  • reason describes why closed is true, should only be non-empty if closed is true
  • nextOpen is optional even when closed is true. A schedule need not ever reopen, so requiring it would be a contract the first implementation breaks. No CEL validation links the two. Treat its absence as "frozen, with no known end" and render reason.
  • nextClose is meaningful only while promotion is allowed, and is likewise optional.
  • nextCloseReason describes a reason for expected next closure, should only be non-empty if nextClose is present.
  • At most one of nextOpen / nextClose appears: only the boundary ahead is reported.
  • Advisory. The value is only as current as the Stage's last reconciliation. Admission remains the sole arbiter of whether a Promotion is permitted, so this should not gate a promote action — a client that treats it as authorization will eventually act on a stale value.

AutoPromotionEnabled is the precedent: likewise a status field derived from ProjectConfig so clients need not recompute policy.

A note on wording

The messages say "promotion freeze" rather than "Deny promotion window", and "promotion window" rather than "Allow promotion window". kind: Allow|Deny remains the configuration vocabulary, where an explicit pair is unambiguous; it just reads poorly in a sentence, and these strings are shown to people.

Testing

API-only; no behavior change. Full codegen (CRD, deepcopy, OpenAPI, generated clients, UI schema and models). api and root modules build and their tests pass. The Stage CRD gains one optional object under status, so the change is additive for existing resources.

@hairyhum
hairyhum requested a review from a team as a code owner August 6, 2026 21:14
@netlify

netlify Bot commented Aug 6, 2026

Copy link
Copy Markdown

Deploy Preview for docs-kargo-io ready!

Name Link
🔨 Latest commit 1752c60
🔍 Latest deploy log https://app.netlify.com/projects/docs-kargo-io/deploys/6a762788571d990008518870
😎 Deploy Preview https://deploy-preview-6772.docs.kargo.io
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@kargo-governance-bot kargo-governance-bot Bot added needs/area Issue or PR needs to be labeled to indicate what parts of the code base are affected needs/kind Issue or PR needs to be labeled to clarify its nature needs/priority Priority has not yet been determined; a good signal that maintainers aren't fully committed labels Aug 6, 2026
@hairyhum hairyhum changed the title Eron wright/promotion schedule status feat(api): report promotion window state on Stage status Aug 6, 2026
@hairyhum hairyhum added area/ui Affects the UI area/api-server Affects Kargo's API server area/crds Affects custom resource definitions kind/enhancement An entirely new feature priority/normal This is the priority for most work and removed needs/kind Issue or PR needs to be labeled to clarify its nature needs/priority Priority has not yet been determined; a good signal that maintainers aren't fully committed needs/area Issue or PR needs to be labeled to indicate what parts of the code base are affected labels Aug 6, 2026
@codecov

codecov Bot commented Aug 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 158 lines in your changes missing coverage. Please review.
✅ Project coverage is 30.95%. Comparing base (2f0ad08) to head (1752c60).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
.../client/generated/model_promotion_window_status.go 0.00% 137 Missing ⚠️
pkg/x/client/generated/model_stage_status.go 0.00% 21 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6772      +/-   ##
==========================================
+ Coverage   30.83%   30.95%   +0.11%     
==========================================
  Files         824      825       +1     
  Lines       80467    98378   +17911     
==========================================
+ Hits        24815    30456    +5641     
- Misses      54196    66466   +12270     
  Partials     1456     1456              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@hairyhum
hairyhum requested a review from rpelczar August 7, 2026 15:11
// NextCloseReason explains in human-readable terms why promotion will be forbidden
// at NextClose, naming the freeze responsible where there is one.
// It is set whenever NextClose is set.
NextCloseReason string `json:"nextCloseReason,omitempty"`

@fuskovic fuskovic Aug 7, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add an +optional marker here?

Suggested change
NextCloseReason string `json:"nextCloseReason,omitempty"`
//
// +optional
NextCloseReason string `json:"nextCloseReason,omitempty"`

@fuskovic fuskovic left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One small nit other than that LGTM

@hairyhum

hairyhum commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

@rpelczar please take a look since you were going to work on the UI part of it.

EronWright and others added 5 commits August 7, 2026 14:44
A Stage gated by a promotion window offers no way to ask whether promotion
is currently possible. The only answer available is the rejection returned
when someone tries and fails, which means a user interface can explain a
freeze only after a user has run into it.

PromotionScheduleStatus reports the evaluated union of every window matching
the Stage: whether promotion is currently forbidden, why, and when that is
next expected to change in either direction. NextClose lets a client warn of
an approaching freeze while promotion is still allowed, rather than only
describing one already in effect.

It follows AutoPromotionEnabled, which is likewise a status field derived from
ProjectConfig so that clients need not recompute policy for themselves.

The field is advisory and says so. It is only as current as the last
reconciliation of the Stage, so admission stays the sole arbiter of whether a
Promotion is permitted; this exists to explain a freeze, not to authorize
anything. Clients that treat it as authorization will eventually act on a
stale value.

Reason describes the freeze being reported rather than the current state
alone, and Closed says which of the two: while Closed it explains the freeze
in effect, and alongside NextClose it explains the freeze that is coming. One
field covers both because in both cases the question a reader has is the same
-- which freeze is this, and why.

NextOpen is optional even while Closed is true. A schedule need not have any
future opening -- a one-shot Allow window that has already elapsed forbids
promotion for good -- so a contract requiring the timestamp would be one the
very first implementation breaks. Reason is therefore the field guaranteed to
explain a freeze in effect, and the godoc tells clients to render it and treat
a missing NextOpen as "frozen, with no known end". Leaving the two unlinked
also lets an implementation express an indefinite freeze honestly rather than
inventing a reopening it cannot predict.

As with PromotionWindow itself, evaluation is Enterprise-only and nothing in
OSS writes this field; OSS carries the API for compatibility, as it does for
other Enterprise-only fields.

Signed-off-by: Eron Wright <eron.wright@akuity.io>
…lds less confusing.

- `promotionSchedule` -> `promotionWindowStatus` - unify naming with `promotionWindows`
- `closed` is non-optional if `promotionWindowStatus` exists for clarity
- `reason` should only describe a reason for closed window, `nextCloseReason` is added to describe a reason for `nextClose` - this is to prevent people from thinking that `reason` describes a reason why it's open.

Signed-off-by: Daniil Fedotov <daniil.fedotov@akuity.io>
Signed-off-by: Daniil Fedotov <daniil.fedotov@akuity.io>
Signed-off-by: Daniil Fedotov <daniil.fedotov@akuity.io>
Signed-off-by: Daniil Fedotov <daniil.fedotov@akuity.io>
@hairyhum
hairyhum force-pushed the EronWright/promotion-schedule-status branch from 4cfce0d to 1752c60 Compare August 7, 2026 18:44

@rpelczar rpelczar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@hairyhum
hairyhum enabled auto-merge August 10, 2026 15:54
@hairyhum
hairyhum added this pull request to the merge queue Aug 10, 2026
Merged via the queue into main with commit 7d2a053 Aug 10, 2026
27 of 29 checks passed
@hairyhum
hairyhum deleted the EronWright/promotion-schedule-status branch August 10, 2026 16:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/api-server Affects Kargo's API server area/crds Affects custom resource definitions area/ui Affects the UI kind/enhancement An entirely new feature priority/normal This is the priority for most work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants