feat(controller): record a Stage's current and last PromotionRequest - #6843
Open
jsbroks wants to merge 1 commit into
Open
feat(controller): record a Stage's current and last PromotionRequest#6843jsbroks wants to merge 1 commit into
jsbroks wants to merge 1 commit into
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #6843 +/- ##
==========================================
+ Coverage 31.04% 31.12% +0.07%
==========================================
Files 835 835
Lines 81453 81579 +126
==========================================
+ Hits 25291 25391 +100
- Misses 54690 54716 +26
Partials 1472 1472 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
jsbroks
force-pushed
the
justin/promotion-request-status-writer
branch
2 times, most recently
from
August 19, 2026 20:19
3663ade to
7ef55e3
Compare
jsbroks
force-pushed
the
justin/promotion-request-status-types
branch
from
August 20, 2026 00:22
0c72185 to
c192ee3
Compare
jsbroks
force-pushed
the
justin/promotion-request-status-writer
branch
from
August 20, 2026 13:49
7ef55e3 to
185329f
Compare
jsbroks
force-pushed
the
justin/promotion-request-status-types
branch
from
August 20, 2026 20:50
c192ee3 to
902d7bb
Compare
jsbroks
force-pushed
the
justin/promotion-request-status-writer
branch
from
August 21, 2026 17:51
185329f to
2ad48c3
Compare
✅ Deploy Preview for docs-kargo-io ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
jsbroks
force-pushed
the
justin/promotion-request-status-writer
branch
2 times, most recently
from
August 24, 2026 14:11
97e19e5 to
456d00a
Compare
jsbroks
marked this pull request as ready for review
August 24, 2026 15:52
fuskovic
requested changes
Aug 25, 2026
fuskovic
left a comment
Member
There was a problem hiding this comment.
If a target-aware Stage whose selector doesn't match anything AND has a pre-existing non-terminal PromotionRequest, could that still populate CurrentPromotionRequest? I'd like to see a test for that. If it does get populated, would that contradict "A Stage that governs no Targets has no PromotionRequests"?
The Stage reconciler gains a syncPromotionRequests step that lists the PromotionRequests promoting on the Stage's behalf and records two of them in status: the one the Stage is promoting through, and the newest to have reached a terminal phase. Selection matches syncPromotions exactly, because the situation is the same one. A Stage can have several PromotionRequests in flight -- auto-promotion creates one only when none exists in any phase, but the promote endpoints create one per call, so consecutive promotions queue up. So the requests are sorted with ComparePromotionRequestByPhaseAndCreationTime, the PromotionRequest counterpart of ComparePromotionByPhaseAndCreationTime -- Running first, then non-terminal by ULID ascending, then terminal by ULID descending -- and the Stage records the first as current, exactly as it takes the first sorted Promotion as its current Promotion. As there, name order stands in for creation order. A generated PromotionRequest name is <stage>.<ulid>.<short-hash>, so among the requests of a single Stage everything left of the ULID is identical and comparing names whole is comparing the ULIDs. (Child Promotion names carry a Target segment before the ULID; PromotionRequest names carry no such segment, so the divergence possible there cannot arise here.) The last PromotionRequest only ever moves forward. Terminal requests sort newest-first, so the first terminal request in the sorted list is the newest, and it is recorded only when it is newer than the one already recorded: a Stage's account of how its last round of fan-out ended should outlive the request that produced it, so garbage collection of the newest request must not let an older one take its place. Supporting pieces: a PromotionRequestsByStage index to list a Stage's requests, and a PromotionRequestPhaseChanged predicate so a phase change -- or the deletion of a non-terminal request, which must clear the current reference -- enqueues the Stage. Both mirror what already exists for Promotions. The two StageStatus fields are no longer documented as ignored in OSS, since this writer is in OSS: OSS maintains them, and the request they refer to simply never gets further than being marked Errored because fanning Freight out to Targets is an Enterprise-only feature. The stale reasoning in createAutoPromotionRequest about status not tracking PromotionRequests is updated too; its stricter guard is unchanged, and still justified. Signed-off-by: Justin Brooks <justin.brooks@akuity.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jsbroks
force-pushed
the
justin/promotion-request-status-writer
branch
from
August 25, 2026 14:30
456d00a to
454160c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Builds on #6842 (merged), which added the types these fields use.
Summary
A new
syncPromotionRequestsstep in the Stage reconciler lists thePromotionRequests promoting on the Stage's behalf and records two of them in
status:
currentPromotionRequest— the request the Stage is promoting through,cleared when there is none, so a finished request never looks active.
lastPromotionRequest— the newest request to have reached a terminal phase.Selection matches
syncPromotions, because the situation is the same oneA Stage can have several PromotionRequests in flight. Auto-promotion creates one
only when none exists in any phase, but
promote_to_stageandpromote_downstreamcreate one per call with no in-flight check, so consecutivepromotions queue up — the same way consecutive Promotions do.
So the requests are sorted with
api.ComparePromotionRequestByPhaseAndCreationTime,the PromotionRequest counterpart of
ComparePromotionByPhaseAndCreationTime(Running first, then non-terminal by ULID ascending, then terminal by ULID
descending), and the Stage records
Items[0]as current — exactly assyncPromotionstakes the first sorted Promotion as its current one. Thatparallel matters beyond cosmetics: the Promotion reconciler gates on
stage.Status.CurrentPromotion.Name == promo.Name, so a Stage's status is whatserializes its queue. Keeping the same shape lets the Enterprise fan-out
reconciler gate on
currentPromotionRequestthe same way.As in
syncPromotions, name order stands in for creation order. A generatedPromotionRequest name is
<stage>.<ulid>.<short-hash>, so among one Stage'srequests everything left of the ULID is identical and comparing names whole is
comparing the ULIDs. Child Promotion names carry a Target segment before the
ULID; PromotionRequest names carry no such segment, so the divergence that bit
there cannot arise here, and no ULID-extraction machinery is needed.
lastPromotionRequestonly moves forwardTerminal requests sort newest-first, so the first terminal request in the sorted
list is the newest. It is recorded only when it is newer than the one already
recorded: a Stage's account of how its last round of fan-out ended should outlive
the request that produced it, so garbage collection of the newest request must
not let an older one take its place.
Other pieces
indexer.PromotionRequestsByStage, to list a Stage's requests.kargo.PromotionRequestPhaseChanged, so a phase change — or the deletion of anon-terminal request, which must clear the current reference — enqueues the
Stage. Both mirror what already exists for Promotions.
freightis aPromotionRequestFreightReferencecarryingonly the Freight's name, copied straight from
spec.freight— no lookup, noerror path, and nothing to go stale if the Freight is garbage-collected.
currentPromotionRequestandlatestPromotionRequest#6842 documented both fields as ignored in OSS; this writer isin OSS, so that note is replaced. OSS maintains the fields, and the request
they refer to simply never gets past being marked
Errored, because fanningFreight out to Targets is an Enterprise-only feature.
createAutoPromotionRequestabout Stage status nottracking PromotionRequests is updated. Its stricter guard is unchanged and
still justified: these fields mirror a request's own phase, not a Stage having
absorbed its outcome.
Testing
TestRegularStageReconciler_syncPromotionRequests— 11 cases: list error,clearing a stale current reference, other Stages' requests ignored, a
non-terminal request becoming current (with its name-only freight reference),
Running over Pending, older Pending over newer, terminal becoming last, both
references at once, and last never moving backwards. Fixtures use
GeneratePromotionRequestName, so the ordering cases run against realgenerated names.
TestComparePromotionRequestByPhaseAndCreationTime(with an antisymmetrycheck per case),
TestComparePromotionRequestPhase,TestPromotionRequestsByStage,TestPromotionRequestPhaseChanged.go test -racegreen overpkg/controller/...,pkg/api/...,pkg/server/...,pkg/garbage/....make lint-go0 issues;codegen-controller/codegen-openapi/codegen-uire-run for the doc-comment changes.