Follow-up to LeanerCloud/cloud-commitments-cli#1668 / LeanerCloud/cloud-commitments-cli#1713, which closed the money hole: the retry endpoint now returns a 409 with ops_hint + redrive_unsafe: true when an execution's recommendations are not safe to re-drive (Azure savings-plans, or an unrecognized provider).
The History UI still renders the Retry button on those rows. It hides the button only when the row carries a server-computed ops_hint (frontend/src/history.ts:603), and handler_history.go computes that field from the failure-reason map alone, which knows nothing about provider re-drive safety. So the operator clicks Retry, the backend refuses, and they get an error toast instead of never being offered the action.
Purely cosmetic now that the backend fails closed, but the button promises something that can never succeed.
Suggested shape: have the History row projection surface the same purchase.RedriveRefusalReason result (as ops_hint plus a redrive_unsafe flag so the badge can word itself as permanent rather than operator-fixable), and let the existing if (p.ops_hint) return false in canRetry do the rest. Touches internal/api/handler_history.go, internal/config/types.go (the OpsHint projection struct), frontend/src/types.ts and frontend/src/history.ts.
Follow-up to LeanerCloud/cloud-commitments-cli#1668 / LeanerCloud/cloud-commitments-cli#1713, which closed the money hole: the retry endpoint now returns a 409 with
ops_hint+redrive_unsafe: truewhen an execution's recommendations are not safe to re-drive (Azure savings-plans, or an unrecognized provider).The History UI still renders the Retry button on those rows. It hides the button only when the row carries a server-computed
ops_hint(frontend/src/history.ts:603), andhandler_history.gocomputes that field from the failure-reason map alone, which knows nothing about provider re-drive safety. So the operator clicks Retry, the backend refuses, and they get an error toast instead of never being offered the action.Purely cosmetic now that the backend fails closed, but the button promises something that can never succeed.
Suggested shape: have the History row projection surface the same
purchase.RedriveRefusalReasonresult (asops_hintplus aredrive_unsafeflag so the badge can word itself as permanent rather than operator-fixable), and let the existingif (p.ops_hint) return falseincanRetrydo the rest. Touchesinternal/api/handler_history.go,internal/config/types.go(theOpsHintprojection struct),frontend/src/types.tsandfrontend/src/history.ts.