Skip to content

feat(gcp): add raw-SKU lookup parity with AWS (RC3-015, #35) - #97

Merged
x7even merged 10 commits into
mainfrom
rc3-015-gcp-sku-lookup
Jul 9, 2026
Merged

feat(gcp): add raw-SKU lookup parity with AWS (RC3-015, #35)#97
x7even merged 10 commits into
mainfrom
rc3-015-gcp-sku-lookup

Conversation

@x7even

@x7even x7even commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds provider="gcp" support to get_price_by_sku, get_prices_by_sku, and the raw-SKU branches of estimate_bom / compare_bom_regions, matching AWS's existing raw-SKU lookup (closes RC3-015, RC3-015 (P2, SCOPED — GCP-first, needs live catalog access to implement): no GCP equivalent of get_price_by_sku exists #35).
  • Hoists the shared lookup types (SKULookupResult, SKULookupRegionResult, error/hint-status constants) into a new internal/skulookup package behind a SKULookupProvider interface, so AWS and GCP implement one contract instead of AWS-only types being reused by convention.
  • GCP's region attribution is geoTaxonomy-first with a serviceRegions fallback (GLOBAL / REGIONAL / MULTI_REGIONAL), because some SKUs (observed in KMS) carry a restrictive serviceRegions list despite being globally priced — plain serviceRegions membership matching alone is unreliable.
  • Before generalizing Firestore's multi-region short-name parser (nam5/nam7/eur3), checked all 13 onboarded GCP services for MULTI_REGIONAL geoTaxonomy usage. Only Firestore needs it — every other service either uses plain literal serviceRegions matching or is pure GLOBAL — so it stays Firestore-specific rather than being hoisted into a shared abstraction prematurely. A live authenticated catalog check (not possible in this environment) is flagged as a follow-up to fully rule out MULTI_REGIONAL on the untested services.
  • An 8-angle code review of the initial implementation surfaced 25 candidate issues, all confirmed real; 24 are fixed here (graduated tiered-rate billing math, region-name case sensitivity, a GCP catalog-fetch cache-stampede risk via singleflight coalescing, dead code removal, several duplicated-logic consolidations). Two were reviewed and left as-is with reasoning in the commit.

Test plan

  • go build ./...
  • go vet ./...
  • gofmt -l . (clean)
  • go test ./... -count=1 (all packages pass, including 9 new TestLookupSKUAcrossRegionsGeneric_* cases in internal/providers/gcp)
  • Traced provider="gcp" end-to-end through get_price_by_sku/get_prices_by_sku and compare_bom_regions' raw-SKU partitioning — reachable with no schema-level blocks

x7even added 2 commits July 7, 2026 00:01
…ions (RC3-004, #31)

Route CUR-style usage-type/SKU strings through processBOMItems (shared by
estimate_bom and compare_bom_regions) via the same AWS SKU resolver
get_price_by_sku already uses, so both tools accept a raw-SKU item
alongside PricingSpec dicts. Scoped to raw-SKU support only; weighting and
a providers filter are deferred.
Extends get_price_by_sku, get_prices_by_sku, and the raw-SKU branches of
estimate_bom/compare_bom_regions to support provider="gcp", matching AWS's
existing raw-SKU lookup. Hoists the shared lookup types/interface into a new
internal/skulookup package so both providers implement one contract.

GCP's region attribution follows a geoTaxonomy-first, serviceRegions-fallback
rule (GLOBAL/REGIONAL/MULTI_REGIONAL), since restrictive serviceRegions lists
on otherwise-global SKUs (observed in KMS) make plain serviceRegions matching
unreliable on its own. Checked all 13 onboarded GCP services for
MULTI_REGIONAL geoTaxonomy usage before generalizing Firestore's multi-region
short-name parser — only Firestore needs it, so it stays Firestore-specific
rather than being hoisted into a shared abstraction prematurely.

Also fixes graduated tiered-rate billing math, region-name case sensitivity,
a GCP catalog-fetch cache-stampede risk (singleflight coalescing), and removes
dead code left behind by the provider-generic refactor, found via an 8-angle
review pass over the initial implementation.
@x7even
x7even marked this pull request as ready for review July 7, 2026 11:17
x7even added 8 commits July 7, 2026 11:35
# Conflicts:
#	opencloudcosts-go/internal/server/server.go
#	opencloudcosts-go/internal/tools/bom.go
#	opencloudcosts-go/internal/tools/bom_test.go
#	opencloudcosts-go/internal/tools/compare_bom_regions.go
#	opencloudcosts-go/internal/tools/compare_bom_regions_test.go
#	opencloudcosts-go/internal/tools/sku_lookup.go
#	opencloudcosts-go/schemas/tools-snapshot.json
get_price_by_sku and get_prices_by_sku already emit tiered/all_tier_rates
for GCP usage-volume-tiered SKUs and document it in their descriptions, but
the OutputSchema for both tools never declared the fields. Found during
merge-conflict review while reconciling PR #96 (raw-SKU BoM line items) with
PR #97 (GCP SKU lookup parity).
…ation

sku_lookup.go, bom.go, and aws_sku_lookup.go still described raw-SKU
lookup as an AWS-only concept in doc comments and one user-facing error
message, left over from before RC3-015 generalized it to also support
provider="gcp". Comment/string-only changes, no logic touched.
Implements Azure as a third provider behind the shared
skulookup.SKULookupProvider interface: fetch once by meterId (no
per-region calls), bucket by ArmRegionName, disambiguate by
IsPrimaryMeterRegion then type/spot-meterName hints, with a
collision-safe tier resolver that reports Ambiguous rather than
guessing when Reservation-type rows share identical
(meterId, region, type, tierMinimumUnits) but price differently -
confirmed against live API data where 86/115 sampled Reservation
groups collided this way.

Wires provider="azure" through get_price_by_sku, get_prices_by_sku,
estimate_bom, and compare_bom_regions raw-SKU items.
Every tool declares an OutputSchema, but the shared jsonText response
helper only ever set Content (unstructured text), never
StructuredContent - a pre-existing gap since the original Go server
rewrite. Strict MCP clients (the official Python SDK, used by the
local test harness) reject every tool call outright when a schema is
declared but structuredContent is absent, which surfaced only now
under harness testing.

Also defaults two nil []string fields (get_price_by_sku/get_prices_by_sku's
no_mapping_in[].attempted_services on Azure's no-mapping path, and the
upstream_failure branch's regions when the caller omits regions) to
empty arrays before they reach the response map - both marshal to JSON
null otherwise, which fails schema validation since both fields are
declared as non-nullable arrays.
Covers the 7 gaps flagged in code review: AWS raw-SKU success, GCP/Azure
no-mapping, ambiguous match, GCP/Azure tiered rates, batch mixed outcomes,
raw-SKU BoM line items, and protocol edge cases (missing region, empty
batch, unsupported provider, oversized batch, blank SKU).

Adds rsku_manifest.json alongside TEST_PROMPTS since the harness has no
per-test expected-outcome schema; the manifest carries expected_outcome
per test ID for post-run grading.
resolveBOMSKUItem (bom.go) silently defaulted a missing provider to
"aws", risking misrouted lookups since BoM calls routinely mix items
from multiple providers in one request. Now returns a clear error
instead. compare_bom_regions.go's independent item-partitioning loop
gets the matching fix, using stringItemField so a non-string provider
value is reported as a type error rather than folded into "missing",
and a shared notSupportedEntry helper plus rawSKUProviderRequiredHint
constant to keep the two call sites' messages from drifting apart.

Also updates the stale field-level items schema description (server.go
and tools-snapshot.json) that still said "provider aws or gcp" and
omitted the required-provider behavior, and strengthens the regression
test to reuse the existing makeComputePrice fixture helper.
go1.25.11's crypto/tls has an Encrypted Client Hello privacy leak
(GO-2026-5856), flagged by CI's govulncheck step. Fixed upstream in
go1.25.12.
@x7even
x7even merged commit 9077693 into main Jul 9, 2026
1 check passed
x7even added a commit that referenced this pull request Jul 9, 2026
go.mod requires go >= 1.25.12 (GO-2026-5856 toolchain bump), but the
Docker builder stage was still pinned to 1.25.11, breaking the
Publish Docker image workflow on every main push and tag since #97.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant