Repository navigation
feat(plans): plan steps resolve real recommendations and show real counts and prices (tier B of #609) #631
Copy link
Copy link
Open
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 #1861, creator stamp #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.
Reactions are currently unavailable
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…
31 remaining items
cristim commentedon Oct 15, 2026
PR #880 normally squash merged as
64ec8b3fe12de04964837a3c68b1957a2c7dccf4, with parent149544feand the exact independently reviewed treeb3514bf0. All required checks were green at the reviewed PR head before merge.All five post-merge workflows passed: full CI, automatic deployment, frontend E2E, frontend build sentinel and pre-commit. Every CI watcher and the completed PR merge watcher has been reaped.
Read-only deployed identity verification returned HTTP200 from
/version, reportinggit_sha: 64ec8b3and build20261015055918. The running Lambda and corresponding image digest matched the same build/source. This verifies deployed identity; no authenticated live plan or purchase was changed.The actual Create/Edit form preview was independently verified locally from the production bundle at desktop and phone widths, including dense schedules, keyboard interaction, focus trapping and restoration, and zero API writes. The browser APIs/authentication were fixtures. This leaf adds the disclosed no-write preview; existing Save and Add Purchases actions remain. Atomic graph scheduling and saved history integration remain tracked in #631 and are not claimed complete by this PR.
feat(plans): edit local draft schedule dates (#882)
cristim commentedon Oct 15, 2026
Reopening per owner decision (2026-10-16) to track the remaining laddering plan-editor work end to end: M2 managed-ramp chain, ramp-ledger retention, legacy-writer guards, M3 atomic save command, preview/Save API routes, frontend Save (E4), and the end-to-end proof. PRs will say "Refs #631" and mirror this issue's labels; the final route-exposure PR closes it.
test(plans): compare the legacy next date at Postgres microsecond pre…
fix(plans): lock the plan before completing a target-met step
test(plans): drop the total arg from the target-met clock fixture call
fix(plans): preserve managed next-root dates on completion (#906)
fix(plans): lock the plan before completing a target-met step
test(plans): drop the total arg from the target-met clock fixture call
fix(plans): lock the plan before completing a target-met step
test(plans): drop the total arg from the target-met clock fixture call
fix(plans): lock the plan before completing a target-met step
test(plans): drop the total arg from the target-met clock fixture call
fix(plans): lock the plan before completing a target-met step (#909)