Repository navigation
Closed
Labels
effort/xlMulti-week / refactorMulti-week / refactorimpact/manyAffects most usersAffects most userspriority/p1Next up; this sprintNext up; this sprintseverity/highSignificant harmSignificant harmtriagedItem has been triagedItem has been triagedtype/featNew capabilityNew capabilityurgency/this-sprintWithin the current sprintWithin the current sprint
Description
Tier B of #609. The tier A stopgap (#618, #616, merged) made empty plan steps fail loudly and show "No recommendations attached". It did not make plans work: until this issue lands, no plan can complete a step, and "Add Purchases" always returns 409.
Goal
A plan created from recommendations (provider, service, term, payment, target accounts, target coverage, ramp) schedules steps that purchase real recommendations, and the planned-purchases list shows real counts, resource types, upfront cost and savings instead of placeholders.
Proposed shape
- Resolve the matching stored recommendations for the plan's provider, service, term, payment and target accounts at execution time, not at plan creation, so a step buys what is current when it runs.
- Size each step from the ramp:
PercentPerStepof the resolved set, per the target-coverage meaning chosen under D1. - Savings Plans steps are sized by
HourlyCommitment, not instance count. - Store the resolved recommendations on the execution, so approval, history and retry see the same rows.
- Planned-purchases preview: compute count, resource types, upfront and savings for pending steps from the current matching recommendations, labelled as an estimate until the step runs.
- Reuse
createPurchaseExecutionsTx(ramp lock chore(deps): Bump style-loader from 3.3.4 to 4.0.0 in /frontend #1861, creator stamp sec(auth): API key permission constraints are silently dropped on create #950), which tier A kept without a production caller. Re-enable "Add Purchases" through it. - Decide the dropped
recommendationsfield ofPlanRequest: honour it (per D2) or stop sending it fromsavePlan(frontend/src/plans.ts). - Keep the tier A guards as the fallback: a step that resolves to nothing fails once and does not advance the ramp.
Open product decisions (block the design)
- D1. Does "target coverage 80%" mean "buy 80% of the matching recommendations" or "reach 80% account coverage"? The second needs a coverage source, which is unknown for Azure.
- D2. Is the snapshot sent by "Plan from N selected" binding, or does the plan filter win?
Known gaps to cover
- Recommendations with no account (Azure org-wide) never match a
cloud_account_id = ANY(...)filter (buildRecommendationFilter). The step resolution must decide how to treat them, and the Opportunities page applies suppressions, account overrides and provider/allowed-account filters on top of the stored list; the plan path must match. - Completed empty rows from before tier A already advanced the ramp. Count them with
status='completed' AND recommendations='[]' AND plan_id IS NOT NULL; they are left in place. - Auto-Purchase-OFF plans only reach execution through a manual approve/run.
- Dashboard still renders $0 for bare rows (fix(dashboard): upcoming purchases show zeros for plan steps with no recommendations #620).
- Azure costs feeding any preview depend on the go#267 unit fix being re-pinned (sec(azure): purchase spend caps and recorded Azure costs use lookback-window amounts (cap can be exceeded; stale rows after the fix) #612).
Acceptance
- A plan like the one reported (Azure compute, 1 year, 80% target coverage, weekly 25% ramp, one target account, Auto-Purchase on) produces steps with non-zero counts and real upfront and savings figures, and a scheduled step executes against real recommendations.
- A step with nothing to buy fails once, with one failed row, and the ramp does not advance.
- Regression tests use the real data shape of that plan and fail on current main.
Metadata
Metadata
Assignees
Labels
effort/xlMulti-week / refactorMulti-week / refactorimpact/manyAffects most usersAffects most userspriority/p1Next up; this sprintNext up; this sprintseverity/highSignificant harmSignificant harmtriagedItem has been triagedItem has been triagedtype/featNew capabilityNew capabilityurgency/this-sprintWithin the current sprintWithin the current sprint
Activity
fix(plans): point plan-step refusal messages at the open tracking iss…
fix(purchase): scope plan fan-out recommendations to each account (#793)
fix(purchase): release plan-step suppressions only when no purchase c…
test(purchase): pin that post-submission failures keep plan-step supp…
feat(purchase): atomic plan-step claim, shared suppressions and accou…
feat(planstep): resolve a plan step to sized, priced recommendations …
feat(purchase): per-plan step commitment cap, auto-purchase kill swit…
feat(purchase): make notified real and pin the scheduler claim to the…
79 remaining items
fix(api): preserve legacy service aliases on plan update (#1123)
cristim commentedon Oct 27, 2026
Follow-ups from the cold review of #1083 (merged as b4994d2). They're low-severity coverage gaps in the clock characterization and didn't block the merge.
completed_atand its error note are only checked as set, not by value. Shiftingcompleted_atby an hour, or changing the note, still passes. BracketCompletedAtwithtime.Now()before and after the call, and pin the note text.last_execution_date, which completion writes atstore_postgres.go~815-816, is never checked.cristim commentedon Nov 4, 2026
UI observation from production (2026-11-04): a plan whose only step says "No recommendations attached" (Count, Upfront and Est. Savings blank) still shows a green "Health: 100" badge on the plan card, and its Pending step has an enabled primary "Run now" button. With nothing attached, the health score and the enabled Run now both suggest the step is ready to buy.
fix(plans): refuse run-now on a step with no recommendations instead …
cristim commentedon Nov 4, 2026
Carried over from #1708 (item 3), to do when plan steps can carry recommendations:
runPlannedPurchaseandretryPurchase(they refuse an empty plan step with a 409), and the disabled-style Run now for steps with no recommendations (fix(plans): Run now on a step with no recommendations promises a no-op but fails with an unexpected error #1695, fix(plans): Plans page polish and run-now follow-ups #1742). Keep their wording aligned with whatever this issue ships.test(api): pin that a plan with no notice window can't schedule purch…
feat(plans): flag plan steps that will not attach and refuse near sta…
cristim commentedon Nov 5, 2026
Claimed by claude-qa-ui-4: S1 merged (#1761). The S2 detailed plan converged and waits on #1746 slice A (purchase.StepCapApplies plus bound-group Resolve) merging. S5 follows S2.
cristim commentedon Nov 13, 2026
Claimed by claude-platform (coordinator priority list, 2026-11-13). Working this in a worktree; will post the landed commit or a blocker here.
cristim commentedon Nov 13, 2026
Plan on main (claude-platform): ~/.claude/projects/cloud-commitments-platform/plans/631-plan-step-real-recs.md. Most of this issue is on main already (resolution at attach, resolved recs stored on the execution, Add Purchases re-enabled, one failed row with no ramp advance). Open item: a step that has not attached yet shows blank count, upfront and savings. Plan: estimate only the next unattached step, through the same planstep.Resolve path the purchase uses, labelled as an estimate; later steps show 'sized when step N-1 runs'. Four commits, no migration. Implementation starts now; this issue closes with the fourth commit.
refactor(api): expose the strict recommendation read to the handler
feat(api): estimate the next unattached plan step from current recomm…
feat(frontend): show the estimate on pending plan steps
cristim commentedon Nov 13, 2026
Landed on main (claude-platform): 22ee7d9, 402d2bb, 6d90c19 and a61acf5 (head a61acf5). The next unattached plan step now shows an estimate labelled as an estimate: the same planstep.Resolve path the attach uses, a view:recommendations gate, the cap check applied, and nothing persisted or read by approval, cap, retry or attach. Reviewed in two passes (approve), with integration tests against Docker showing the estimate equals what the tick later attaches. Open follow-ups, non-blocking: a unit test for an already-attached row at the next step (guards against double counting), and a test asserting one Resolve call per plan per request. The dropped PlanRequest recommendations field stays with #1746.