Skip to content

feat(insurance): consume pkg/insurance for an explicit Archera comparison (backend, API, config, UI) #785

Closed

Description

@cristim

Goal

The platform backend and UI consume the merged Archera comparison contract pkg/insurance (cloud-commitments-go #285, closes go#284). An authenticated, account-authorized user can explicitly request Archera's insured-commitment comparison for a configured Archera plan and see it next to the platform's own recommendations. Today nothing consumes pkg/insurance.

Contract being consumed (cloud-commitments-go pkg/insurance, main)

  • NewClient(Config{APIKey, OrgID}, hc *http.Client) (*Client, error): fixed origin https://api.archera.ai, x-api-key header, redirects never followed, UUID-validated IDs, 8 MiB response bound. Config and Client redact the key in fmt output, except for %p on a value. Never log either value with %p, and never keep a Config in an unexported field that gets logged.
  • (*Client).Comparison(ctx, ComparisonRequest{PlanID, LineItemIDs, ContractTerms, PaymentOptions}) (*Comparison, error): the documented GET /v1/org/{org_id}/commitment-plans/{plan_id}/comparison.
  • (*Client).Plan(ctx, planID) (*Plan, error).
  • *HTTPError{StatusCode, RetryAfter, Message}: sanitized, Retry-After capped at 24h, and the client never retries.
  • AssessProductSupport(provider, commitmentType) ProductSupport: supported only for an exact documented row; everything else is unknown.
  • Money is *big.Rat. A nil value means unknown, not zero. Premium is already included in totals. There is no currency field.

Scope

  1. Dependency: bump github.com/LeanerCloud/cloud-commitments-go/pkg from a9a426224031 (which predates pkg/insurance) to a merged commit that contains fix(frontend): post-exchange refresh drops the RI account scope and skips reshape recs #285 (73d3366 or later). Use tidy only, with no other module upgrades.
  2. Configuration, all default off:
    • ARCHERA_ORG_ID and ARCHERA_PLAN_ID are plain env config. These are Archera IDs, not local recommendation or plan IDs.
    • The API key goes through the existing server secret resolver (internal/secrets, SECRET_PROVIDER), following the repo's *_SECRET convention. The proposed name is ARCHERA_API_KEY_SECRET, holding the secret reference. The key is never in plain env, flags, request bodies, browser state, response JSON, audit records or logs.
    • With no config, the comparison surface is disabled: no route behaviour and no outbound call.
    • With incomplete config, an explicit request returns a clear, sanitized configuration error, never a silent success.
  3. Backend endpoint: an authenticated, permission-checked, read-only handler in internal/api (for example GET /api/insurance/comparison).
    • Construct a pkg/insurance.Client once per process from the resolved config and inject it behind the QuoteClient interface.
    • No implicit calls on startup, and none on the recommendation or purchase paths.
    • Map *HTTPError and errResponseTooLarge to sanitized client errors, and surface RetryAfter.
  4. Response shape: an explicit platform DTO, not the raw contract structs.
    • Keep nil money as JSON null (unknown), never 0.
    • Carry TermReason and fallback reasons.
    • Say that there is no currency field and that premium is included.
    • Make no "insured" or "guaranteed" claim the contract does not make.
  5. Fail loud on unknown products: every row shown with a platform recommendation carries AssessProductSupport(provider, commitmentType). unknown (the default) is shown as "not known to be supported by Archera", never as supported. Do not add rows to the eligibility table here; that table belongs to go.
  6. UI: an explicit "Compare with Archera" action next to the existing Archera content (frontend/src/archera.ts). It is shown only when the backend reports the surface as configured. Existing mandatory Archera disclosures stay visible.
  7. IaC: wire the secret and the two IDs through the per-cloud compute modules (AWS, Azure, GCP), following how the existing *_SECRET values are provisioned. Leave them empty by default.

Acceptance criteria

  • With no Archera config: no outbound request, the endpoint reports "not configured", and no UI action appears.
  • With incomplete config (any one of the key, org or plan missing): the explicit request returns a sanitized configuration error that names the missing setting and contains no secret material.
  • With complete config, an offline test traverses the real production composition (config, secret resolver, insurance.NewClient, handler, JSON response) against an httptest server through an injected transport. It asserts:
    • the documented GET path, the x-api-key header and the query encoding;
    • that premium-inclusive totals survive;
    • that unknown money is null;
    • that term reasons and fallback reasons are present.
  • Unknown product support is rendered as unknown. A test with a product outside the documented rows asserts this.
  • Vendor 401, 404, 429 (with Retry-After) and 5xx become sanitized client errors. An oversized response fails loud.
  • The key never appears in logs, errors, response JSON or audit rows. A test greps the captured output for the synthetic key.
  • The permission and account scope checks match the existing recommendation-read permission. A test covers a denied user.
  • Docs: the configuration and disclosure text are added to the deployment docs.

Test strategy

Use recorded or synthetic responses only, built from the official schema (the shapes in pkg/insurance tests), served by httptest. Use a synthetic key and IDs. No live calls to api.archera.ai, and no purchases, binding or customer submissions. Go tests run one package at a time with no -race, per the current disk rules. The UI path runs locally against the httptest-backed server.

Dependencies

  • First consumer in the order (see the tracking issue). The CLI and MCP issues reuse the config names and error wording settled here.
  • Requires the pkg pin bump in scope item 1.

Open contract questions (answer before planning)

  1. Is the key secret reference named ARCHERA_API_KEY_SECRET (repo convention), or should the platform also accept a plain ARCHERA_API_KEY for local dev only?
  2. Is the org and plan global per deployment, or per cloud account (multi-tenant mapping)? The MVP proposal is global.
  3. Should the endpoint pass ContractTerms and PaymentOptions filters through from the UI, or use the documented defaults only?
  4. Which permission gates the endpoint: the recommendations read permission, or a new view:insurance?

Activity

  1. cristim commented on Oct 9, 2026

    @cristim
    MemberAuthor

    Tracked in #786 (dependency order, shared config names and open contract questions are there).

  2. cristim commented on Oct 10, 2026

    @cristim
    MemberAuthor

    Scope addition (owner request, gap 3 from the Archera gap review): the comparison response has no TTL and no is_calculating flag (pkg/insurance types.go:21-22,206-207); only Plan() carries IsCalculating (types.go:232-234). The UI must show FetchedAt, offer an explicit refresh, and decide whether to call Plan() to show a non-final 'Archera is recalculating this plan' state. Contract answer H on the bus recommends no caching. Tracked in #786.

  3. cristim commented on Oct 12, 2026

    @cristim
    MemberAuthor

    All scope items are on main: pin bump #787, settings/lazy client/status #795, DTO #796, route and error mapping #797, IaC #794, UI #801 (merged as 988e545). Acceptance checked against origin/main: no-config and incomplete-config paths (handler_insurance_test.go, handler_insurance_comparison_test.go 503 cases); offline production-composition test through an injected RoundTripper asserting the GET path and x-api-key (handler_insurance_comparison_test.go:85-97); null-not-zero money and unknown product support (internal/archera/dto_test.go:38,100,112); vendor 401/403/404/429/5xx and oversized body (handler_insurance_comparison_test.go:199-206); synthetic-key grep (internal/archera/provider_test.go:103-149); denied and scoped callers (TestInsuranceComparison_DeniedAndScoped, TestInsuranceStatus_DeniedUserGets403); deployment docs (docs/DEPLOYMENT.md). UI success rendering is fixture-verified only; no live Archera calls.

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