fix(gcp-to-aws): withhold Discover cost range when Terraform sizes exceed preview defaults - #276
Conversation
…ceed preview defaults Co-authored-by: Cursor <cursoragent@cursor.com>
…ed-size gate Instance templates, MIGs, and Dataflow jobs are not PRIMARY, so a PRIMARY-only scan left those table rows unreachable. Also lock signal order and require disclaimer on suppress. Co-authored-by: Cursor <cursoragent@cursor.com>
leon1418
left a comment
There was a problem hiding this comment.
[🤖 AI review 🤖]
Reviewed: all 4 changed files (+84/−8), head 10dd1f9.
Cross-plugin parity: discover-preview.md — byte-identical (blob ac570aa2) ✅. report-decision-core.md — different blob SHAs, but the sole divergence is the pre-existing fixture path on line 40 (advisor/plugins/... vs migrate/plugins/...); the cost-headline change on line 63 is identically worded in both copies ✅. Drift allowlist: 0 changes ✅.
Design: Sound. The authored-size gate addresses a real trust failure — quoting dev-tier stub dollars against production-sized Terraform is misleading by an order of magnitude. The gate is properly a hard pre-check before any dollar range is written, not a post-hoc disclaimer. The field contract (quote_suppressed / quote_suppressed_reason / authored_size_signals / disclaimer) is cleanly separated from the existing aws_monthly_range_usd path so both consumers (the chat COST_ROW template and report-decision-core.md) can branch on a single boolean. Scanning SECONDARY types for instance templates, MIGs, and Dataflow is correct — these are not Priority-1 PRIMARY but still carry sizing information that would blow past the dev-tier stub.
Functionality: Gate table thresholds are well-chosen for their purpose (detecting "clearly not a toy stack"). The fixed type ordering for authored_size_signals prevents non-deterministic output. The max-5 cap is reasonable for chat display. The report-decision-core.md change correctly falls through to the existing "Early estimate (±30%)" path only when the quote is not suppressed, and correctly refuses to render dollar headlines when it is.
Nit: The Step 5 JSON example only demonstrates the non-suppressed case. A second example (or a comment block) showing the suppressed shape (aws_monthly_range_usd: null, quote_suppressed: true, authored_size_signals: [...]) would make the contract more copy-pasteable for someone implementing a consumer. The field rules below the example do cover the suppressed case fully, so this is documentation polish, not a correctness gap.
Tests/CI: CI 8/8 SUCCESS (build, gitleaks, bandit, semgrep, checkov). mise run lint:md and mise run fmt:check reported PASS per the PR description. mise run drift:check OK (273 identical / 25 allowlisted). Markdown-only change — no runtime code to unit-test.
Result: CLEAN — 0 mandatory blockers, 1 Nit (JSON example completeness). MERGEABLE pending maintainer approval.
|
Addressed the nit in 9b5e126. Added a suppressed-quote example to Step 5 in both Files are byte-identical; |
|
Fixed in 609773f — gitleaks was pattern-matching on the |
Adds a second example block to the migration-preview.json Step 5 section demonstrating the authored-size gate firing. Shows the full suppressed shape: aws_monthly_range_usd null, quote_suppressed true, authored_size_signals in fixed type order (max 5), and the gate disclaimer. Uses a realistic large-stack fixture (Cloud SQL REGIONAL, Redis HA, GKE, Cloud Run) to contrast with the toy-stack example. Signal format uses space-separated 'address: field value' throughout (example block and step-3 prose) to avoid gitleaks false positives. Both advisor/ and migrate/ copies updated identically. drift:check OK (273 identical / 25 allowlisted).
609773f to
456b427
Compare
leon1418
left a comment
There was a problem hiding this comment.
[🤖 AI review 🤖]
Follow-up review of 456b427: the suppressed-quote example is now present. A closer check against the Terraform Google provider definitions found three gaps in the gate, so this updates my earlier CLEAN assessment.
The inline comments cover Cloud Run v1 minimum-instance annotations, GKE node-count fields, and inconsistent Spanner capacity thresholds. Each case can let a deployment above the intended preview limits retain the development-tier quote. Please address these in both plugin copies before merging and add regression cases for the relevant field variants, including equivalent Spanner capacities expressed in nodes and processing units.
Validation: reviewed all four changed files, traced inventory extraction and report consumers, and checked the provider/API definitions. Both plugin copies have equivalent changes, and the current commit has eight successful CI checks. I did not run an end-to-end LLM migration; these findings come from checking the specified rules against valid input configurations.
…ze gate Addresses three round-2 review findings on the Discover authored-size gate (discover-preview.md), all stemming from the same root cause: a size expressed in an equivalent but differently-shaped Terraform form could bypass the gate. 1. Cloud Run min instances: google_cloud_run_service (v1) has no min_instance_count field at all -- its minimum-instance setting is the template.metadata.annotations["autoscaling.knative.dev/minScale"] or metadata.annotations["run.googleapis.com/minScale"] annotation. A v1 service authored with minScale = "50" matched none of the gate's conditions and kept the single-instance stub quote. Added a normalization note and a Cloud Run extraction block to discover-iac.md Step 1 (matching the existing Cloud SQL normalization pattern) so min_instance_count is always populated at the canonical field name regardless of API version. 2. GKE node count: a node pool with a fixed node_count/initial_node_count and no autoscaling block, or one using the cluster-wide total_min_node_count/total_max_node_count form, matched none of the listed conditions. Extended the gate condition to cover all three sizing forms and added the corresponding extraction to discover-iac.md. 3. Spanner capacity: num_nodes and processing_units are two ways to express the same capacity (1 node = 1000 PUs, mutually exclusive in Terraform), so num_nodes = 1 was accepted while the equivalent processing_units = 1000 was rejected. Added an explicit normalization step (processing_units / 1000) before the threshold comparison. Added a "Worked normalization cases" table pinning the expected fire/ no-fire outcome for each variant (this spec has no executable test harness -- it is an LLM-instruction file -- so the pinned table is the regression-coverage equivalent requested in review). Both advisor/ and migrate/ copies updated identically (byte-identical apart from the allowlisted path strings elsewhere in the tree). Verified: fmt:check clean, drift:check OK (273/25), lint:md shows only the pre-existing unrelated error in migration-watchdog/.
|
Fixed in 2e5414f. All three edge cases confirmed real: 1. Cloud Run v1 minScale annotation (P2) — added a normalization note in 2. GKE fixed node_count / total_min_node_count / total_max_node_count (P2) — extended the gate condition to cover all three sizing forms (fixed count, per-zone autoscaling, cluster-wide autoscaling) and added the corresponding 3. Spanner num_nodes vs processing_units (P2) — added an explicit normalization step (1 node = 1,000 PUs, so divide processing_units by 1000) before the threshold comparison, so Since this is a markdown instruction spec with no executable test harness, I added a "Worked normalization cases" table right after the gate table pinning the expected fire/no-fire outcome for each variant (v1 annotation forms, all three GKE sizing forms, and the Spanner unit-equivalence case) — this is the regression-coverage equivalent for a spec file. Both copies stay byte-identical. |
leon1418
left a comment
There was a problem hiding this comment.
[AI review]
Re-reviewed 50421e6, including the fixes described in 2e5414f and all six changed files.
The original Cloud Run v1 annotation examples and standalone GKE fixed/total-count examples are now covered. Spanner's equivalent node/PU inputs also produce the same result under the revised >1-node threshold. The worked cases make these expectations clearer.
Three normalization gaps remain, detailed inline: Cloud Run v2 revision-level minimum instances, GKE node pools declared inside the cluster resource, and per-zone GKE counts being compared directly with total counts. These are valid Terraform configurations that can still retain the development-tier quote despite exceeding the intended preview limits. Please extend the extraction rules, gate, and worked cases in both plugin copies to cover them.
Validation: checked the official Google provider documentation and GKE provider schema; verified that both discover files are byte-identical across plugins and that the report copies differ only in their expected fixture path. The current head has eight successful CI checks. This was a specification/source review, not an end-to-end LLM execution. Keeping Request changes for the remaining cases.
…cover leon1418 review (PR awslabs#276): three valid Terraform provider paths let a production-sized stack keep the dev-tier preview quote. 1. Cloud Run v2 revision-level scaling — a v2 service can set its minimum on template.scaling.min_instance_count, not just service-level scaling.min_instance_count. Added to extraction (discover-iac.md), the preview gate normalization, and a worked case. 2. Inline GKE node_pool blocks — google_container_cluster can declare pools inline as node_pool { ... } blocks rather than standalone google_container_node_pool resources; inline blocks are not separate resources, so a per-resource scan misses a 20-node inline pool. Extraction now traverses every inline pool (machine_type, counts, node_locations) and the gate + a worked case cover it. 3. Per-zone vs total node counts — min_node_count/max_node_count are per-zone; the gate compared them against the same >2 threshold as pool totals. Now converts per-zone counts to totals (× node_locations zone count) before comparing. Fixed the previously unsafe single-zone max=2 "No" worked case and added a 3-zone (=6) "Yes" case. Applied identically across advisor + migrate copies (byte-identical, verified); cross-plugin-drift OK; dprint check clean.
|
Addressed all three remaining normalization gaps in 1. Cloud Run v2 revision-level scaling. A v2 service can set its minimum on 2. Inline GKE 3. Per-zone vs total node counts. Applied identically across both plugin copies (byte-identical, verified). Note on the "regression cases" request: this authored-size gate is specified through the worked-case tables rather than an executable fixture harness (there's no test suite wired to the preview gate, unlike the report validator), so I added the cases there — consistent with how the earlier rounds were addressed in |
leon1418
left a comment
There was a problem hiding this comment.
[AI review]
Re-reviewed 675a73b against 50421e6.
Confirmed that Cloud Run v2 revision-level minimum instances and inline GKE node-pool extraction are now covered in the extraction rules, gate, and worked cases. The explicit three-zone autoscaling case is also fixed.
One P2 remains in the existing GKE normalization discussion: the new rule incorrectly treats fixed node_count / initial_node_count as pool totals. They also need zone normalization for regional or multi-zonal pools, so a fixed two-nodes-per-zone pool across three zones still retains the two-node preview quote. I replied in that thread with the provider field semantics and the two missing worked cases.
The two discover files remain byte-identical across plugins; the report copies differ only in their expected fixture path. This was a specification/source review, not an end-to-end LLM run. The worked-case tables are sufficient for this scoped fix; I am not requesting a new executable harness as a merge prerequisite.
Keeping Request changes for that remaining correction.
CI at review time: 7 successful, 1 pending, 0 other completed checks.
leon1418 review at 675a73b: the prior fix wrongly treated fixed `node_count` / `initial_node_count` as pool-wide totals. Per the Google provider they are per-zone (node_count per instance group, initial_node_count per zone, including the cluster default pool), so a regional `e2-small` pool with `node_count = 2` across three node_locations is 6 nodes but kept the two-node preview quote. Apply the `× node_locations` zone multiplier to `node_count`, `initial_node_count`, `min_node_count`, and `max_node_count`; only `total_min_node_count` / `total_max_node_count` stay unchanged as pool totals. Updated the GKE gate row and added worked cases for fixed `node_count = 2` and `initial_node_count = 2` across three zones (both = 6, fire). Both plugin copies updated in lockstep (byte-identical, verified); cross-plugin-drift OK; dprint check clean.
|
Addressed the remaining P2 in The prior fix wrongly treated fixed Fix:
Kept the worked-case approach per your note that an executable harness can be a follow-up. Both plugin copies updated in lockstep (byte-identical, verified); |
leon1418
left a comment
There was a problem hiding this comment.
[AI review]
Re-reviewed 80fbf35, including the final correction in de0d3fb. The remaining GKE finding is addressed: node_count, initial_node_count, min_node_count, and max_node_count all receive the zone multiplier, while total_min_node_count / total_max_node_count remain unchanged. Both requested fixed-count examples now explicitly evaluate to six nodes across three zones and suppress the quote.
The earlier Cloud Run v1/v2 extraction, standalone and inline GKE pool handling, and Spanner unit-equivalence fixes remain intact. I have no remaining blocking findings from this review and approve this scoped fix.
Verification: all three changed file pairs are byte-identical across the advisor and migrate plugins, and all nine checks on the current head have passed. This was a specification/source review, not an end-to-end LLM migration run. The worked cases are sufficient for this PR; an executable preview harness can remain a follow-up.
Problem
Discover's migration preview always prices PRIMARY resources at a hardcoded development-tier stub (0.5 vCPU Fargate,
db.t4g.micro/ 20 GB single-AZ,cache.t4g.micro, 2×t4g.smallEKS nodes) and prints that sum as AWS cost (rough) with a ±30% label.On production-sized Terraform that is a trust failure. A stack with
db-custom-32-122880REGIONAL HA + 2 TB, Memorystore STANDARD_HA 100 GB, Cloud Run min 50 × 8 vCPU, and 6×e2-standard-16GKE nodes is a ~$44k–$54k/mo GCP bill; the preview still quotes ~$175–$263/mo. Users compare that to their real bill (orCOST_ESTIMATE.md) and conclude the tool is broken.±30% does not cover an order-of-magnitude miss. The stub map is still useful for tiny stacks; it must not be the headline when authored sizes clearly exceed it.
Solution
Add an authored-size gate to
discover-preview.md(infra route Step 2) in both plugin copies:configfor every type in the gate table, including SECONDARY. Instance templates, MIGs, and Dataflow jobs are not Priority-1 PRIMARY; a PRIMARY-only scan leaves those rows dead letter.db-f1-micro/db-g1-small, REGIONAL, disk > 20 GB, or replicas; Redis > 1 GB or HA; GKE/GCE/instance-template machine type not*micro*/*small*or node count > 2; MIGtarget_size> 2 or linked autoscalermin_replicas> 2; anygoogle_dataflow_job; Filestore/Spanner above the stub):cost_preview.aws_monthly_range_usdtonull,quote_suppressed: true,quote_suppressed_reason: "authored_sizes_exceed_preview_defaults", and up to 5authored_size_signalsin fixed type order (Cloud SQL → Redis → GKE/node pool → Cloud Run → Dataflow → MIG/template → others).disclaimeris required on suppress (gate sentence, not the stub ±30% line).db-f1-micro, etc.) keep the existing ~$X–$Y range.report-decision-core.md(both copies): if only the preview exists and the quote is suppressed, do not render a dollar headline or "Early estimate (±30%)". Render the withheld-at-Discover sentence instead. Estimate artifacts still supersede the preview.No JSON Schema change:
tsc --noEmitis the frontmatter validator; the preview contract lives in the markdown field list.Type of Change
Team Folder
advisor/migrate/solution-architecture/Scope
In: Discover preview cost row +
migration-preview.jsoncost_previewcontract; decision-report cost headline when Estimate has not run.Out: Changing Design/Estimate defaults (still development-tier unless Clarify keeps authored sizes). Pricing the authored production stack at Discover time. Heroku Discover (no equivalent stub-dollar preview). Adding a JSON Schema for
migration-preview.json.Verification
mise run lint:md: PASS (0 errors / 874 files)mise run fmt:check: PASS (dprint fmtapplied to the twodiscover-preview.mdcopies)mise run drift:check: OK (273 identical / 25 allowlisted)quote_suppressed: trueandaws_monthly_range_usd: nullinstead of ~$175–$263mise run buildsecurity scanners (bandit / semgrep / gitleaks / checkov / grype) not re-run locally — markdown-only change; CI will cover themChecklist
mise run lint:mdandmise run fmt:checklocally and they pass (fullmise run buildsecurity suite left to CI; markdown-only)advisor/andmigrate/, per the drift-gate mirroring requirement)By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
Made with Cursor