Skip to content

ARCH-02: 77-line PurchaseCommitment + pricing helpers copy-pasted across 7-8 Azure service clients #9

Description

@cristim

Severity: P2. Confidence: high.

Affected files:

  • providers/azure/services/database/client.go:287-364
  • providers/azure/services/cache/client.go:289-364
  • providers/azure/services/database/client.go:587
  • providers/azure/services/cache/client.go:602
  • providers/azure/services/internal/reservations/purchase.go:372

Evidence:
A diff of database vs cache shows the entire PurchaseCommitment body (guards, ParseTermYears, request-body map, token fetch, DoIdempotentPurchaseTwoStep call) is byte-identical except "SqlDatabase" -> "RedisCache" and Service: "sql" -> "redis". The same shape repeats across search, cosmosdb, compute, synapse, managedredis (7 service-client callers of DoIdempotentPurchaseTwoStep). azureTermString is duplicated character-for-character in 6 files; fetchAzurePricing is re-implemented in 5 clients, each with its own near-identical RetailPriceItem struct. Only the pagination loop was centralized, whose own doc comment concedes the copy problem.

Impact:
Seven copies of a money-path purchase function means any fix (the ARCH-01 enum fix, a new required field, idempotency change) must be applied 7 times; the two-step-flow PR LeanerCloud/cloud-commitments-cli#680 already had to touch 7 clients. Divergence has demonstrably begun (the issue-LeanerCloud/cloud-commitments-cli#1020 fail-loud pricing guard exists in 5 clients but not managedredis/synapse).

Recommendation:
Extract a shared reservations.BuildPurchaseBody(...) plus a shared PurchaseCommitment template into providers/azure/services/internal/reservations (where DoIdempotentPurchaseTwoStep already lives); move azureTermString and a generic RetailPriceItem + fetchAzurePricing wrapper next to pricing.FetchAll.

Verifier verdict: confirmed - duplication and LeanerCloud/cloud-commitments-cli#1020-guard divergence verified byte-level; corrected the caller count to 7 (savingsplans uses a separate flow); the unmitigated layer is exactly the request-body construction where the PR-LeanerCloud/cloud-commitments-cli#1047-class enum literals live.

Source: docs/reviews/codebase-review-2026-06-10.md (automated multi-dimension code review, adversarially verified for P1/P2)

Findings from the 2026-09-02 codebase audit

Added by an automated audit of 3c0f8ac94048a2c36fce5ccddee54e6c4849a5cd (tip of origin/main). Each item below was reported by one reviewer and independently confirmed by a second that did not write it. Full report: docs/audits/codebase-audit-2026-09-02.md.

A15-008 (low)

Two updates on the helper half of this issue, both of which change what a fix has to touch. First, azureTermString is declared in exactly six packages (cache/client.go:591, compute:757, cosmosdb:589, database:609, managedredis:511, search:502), but synapse makes seven: it recomputes the identical rule inline at synapse/client.go:458-461 rather than calling a helper, so a name-based search under-reports it. All seven encode the Retail Prices API's "1 Year" / "N Years" spelling, so a fix landed only on the named copies leaves extractSynapsePricing silently finding no reservation price if that spelling ever changes. All seven clients already import providers/azure/internal/pricing, so the shared home is reachable without a new dependency. Second, on the pricing types: cache, cosmosdb, database and managedredis now alias pricing.RetailPriceItem, but synapse declares its own SynapseRetailPriceItem (client.go:110-122) omitting both Location and UnitOfMeasure, and search re-declares the envelope (:115-119) instead of aliasing pricing.Page. A field added to the shared item -- unit of measure is the one currently wanted -- decodes to zero in those two. (audit findings A15-008 and A08b-040)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions