Problem
run_policy, on_failure, and retries are documented as applying to validate, each builds entry, and each deploys entry. The promote generator references none of them:
$ grep -nE "Retries|OnFailure|RunPolicy" internal/generate/promote.go
(no matches)
A promotion therefore runs each deploy exactly once, with no retry and no on_failure handling, while a trunk run honors all three. Promote is the production path, so a transient deploy failure is costliest exactly where the retry is missing.
Why it was not simply wired up
The orchestrate retry ladder re-invokes the whole job. That does not transfer to promote as-is: a promote deploy declaring inputs compiles to a matrix job fanned across environments (writeDeployJobs), and a matrix job exposes a single aggregate result, failure if any leg failed. A caller-side shim gated on that aggregate would re-invoke the callback for every environment, redeploying the ones that already succeeded. GitHub Actions has no caller-side way to re-run only the failed legs of a dependency's matrix.
Emitting a ladder only on the non-matrix path was also rejected: it would make retries silently work or not work depending on whether the deploy happened to declare inputs.
Scope taken instead
The documented claim was scoped to trunk runs so the docs are true today (see docs/src/content/docs/reference/manifest.md, Policy fields).
What a real fix needs
- Retry scoped per environment rather than per job, so a healthy environment is never redeployed because a different one failed.
- An effective-result design for promote's own result sinks, which have the same immutable-result hazard as orchestrate: the rollback gate (
needs.deploy-<name>.result == 'success' / == 'failure') and DEPLOY_RESULT_<NAME>. - The rollback interaction settled: rollback must fire only when a ladder is exhausted, not when a first attempt failed and a retry rescued it.
on_failure and run_policy decided at the same time; they share the gap.
Problem
run_policy,on_failure, andretriesare documented as applying tovalidate, eachbuildsentry, and eachdeploysentry. The promote generator references none of them:A promotion therefore runs each deploy exactly once, with no retry and no
on_failurehandling, while a trunk run honors all three. Promote is the production path, so a transient deploy failure is costliest exactly where the retry is missing.Why it was not simply wired up
The orchestrate retry ladder re-invokes the whole job. That does not transfer to promote as-is: a promote deploy declaring
inputscompiles to a matrix job fanned across environments (writeDeployJobs), and a matrix job exposes a single aggregateresult,failureif any leg failed. A caller-side shim gated on that aggregate would re-invoke the callback for every environment, redeploying the ones that already succeeded. GitHub Actions has no caller-side way to re-run only the failed legs of a dependency's matrix.Emitting a ladder only on the non-matrix path was also rejected: it would make
retriessilently work or not work depending on whether the deploy happened to declareinputs.Scope taken instead
The documented claim was scoped to trunk runs so the docs are true today (see
docs/src/content/docs/reference/manifest.md, Policy fields).What a real fix needs
needs.deploy-<name>.result == 'success' / == 'failure') andDEPLOY_RESULT_<NAME>.on_failureandrun_policydecided at the same time; they share the gap.