Repository navigation
feat(insurance): consume pkg/insurance for an explicit Archera comparison (backend, API, config, UI) #785
Copy link
Copy link
Closed
Labels
effort/lWeeksWeeksimpact/fewLimited audienceLimited audiencepriority/p2Backlog-worthyBacklog-worthyseverity/mediumModerate harmModerate harmtriagedItem has been triagedItem has been triagedtype/featNew capabilityNew capabilityurgency/this-sprintWithin the current sprintWithin the current sprint
Description
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 originhttps://api.archera.ai,x-api-keyheader, redirects never followed, UUID-validated IDs, 8 MiB response bound.ConfigandClientredact the key in fmt output, except for%pon a value. Never log either value with%p, and never keep aConfigin an unexported field that gets logged.(*Client).Comparison(ctx, ComparisonRequest{PlanID, LineItemIDs, ContractTerms, PaymentOptions}) (*Comparison, error): the documentedGET /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:supportedonly for an exact documented row; everything else isunknown.- Money is
*big.Rat. A nil value means unknown, not zero. Premium is already included in totals. There is no currency field.
Scope
- Dependency: bump
github.com/LeanerCloud/cloud-commitments-go/pkgfroma9a426224031(which predatespkg/insurance) to a merged commit that contains fix(frontend): post-exchange refresh drops the RI account scope and skips reshape recs #285 (73d3366or later). Use tidy only, with no other module upgrades. - Configuration, all default off:
ARCHERA_ORG_IDandARCHERA_PLAN_IDare 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*_SECRETconvention. The proposed name isARCHERA_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.
- Backend endpoint: an authenticated, permission-checked, read-only handler in
internal/api(for exampleGET /api/insurance/comparison).- Construct a
pkg/insurance.Clientonce per process from the resolved config and inject it behind theQuoteClientinterface. - No implicit calls on startup, and none on the recommendation or purchase paths.
- Map
*HTTPErroranderrResponseTooLargeto sanitized client errors, and surfaceRetryAfter.
- Construct a
- Response shape: an explicit platform DTO, not the raw contract structs.
- Keep nil money as JSON
null(unknown), never0. - Carry
TermReasonand 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.
- Keep nil money as JSON
- 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. - 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. - IaC: wire the secret and the two IDs through the per-cloud compute modules (AWS, Azure, GCP), following how the existing
*_SECRETvalues 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 anhttptestserver through an injected transport. It asserts:- the documented GET path, the
x-api-keyheader and the query encoding; - that premium-inclusive totals survive;
- that unknown money is
null; - that term reasons and fallback reasons are present.
- the documented GET path, the
- 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)
- Is the key secret reference named
ARCHERA_API_KEY_SECRET(repo convention), or should the platform also accept a plainARCHERA_API_KEYfor local dev only? - Is the org and plan global per deployment, or per cloud account (multi-tenant mapping)? The MVP proposal is global.
- Should the endpoint pass
ContractTermsandPaymentOptionsfilters through from the UI, or use the documented defaults only? - Which permission gates the endpoint: the recommendations read permission, or a new
view:insurance?
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
effort/lWeeksWeeksimpact/fewLimited audienceLimited audiencepriority/p2Backlog-worthyBacklog-worthyseverity/mediumModerate harmModerate harmtriagedItem has been triagedItem has been triagedtype/featNew capabilityNew capabilityurgency/this-sprintWithin the current sprintWithin the current sprint
Activity
cristim commentedon Oct 9, 2026
Tracked in #786 (dependency order, shared config names and open contract questions are there).
chore(deps): bump cloud-commitments-go/pkg to 73d3366 (pkg/insurance) (…
fix(iac): take the short GCP secret ID for Archera, quote required di…
fix(insurance): annotate the setting-name constant for gosec G101
feat(insurance): add default-off Archera settings, lazy client and st…
feat(iac): wire optional Archera comparison settings with single-secr…
cristim commentedon Oct 10, 2026
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.
test(insurance): pin every DTO field mapping with a distinct-value go…
test(insurance): add a distinct-value composition test for the compar…
fix(insurance): do not imply a currency in the DTO basis note
feat(insurance): add the Archera comparison DTO with exact decimals (#…
test(insurance): cover Retry-After through the real request path and …
feat(insurance): add the Archera comparison route and error mapping (#…
fix(frontend): fix duplicate 429 text and cover every money and vendo…
test(frontend): cover fetched-at zones, aria-busy reset and empty ven…
test(frontend): kill remaining comparison view mutants found by a ful…
test(frontend): cover multi-item tables and Refresh replacing the view
feat(frontend): explicit Archera comparison section (#801)
cristim commentedon Oct 12, 2026
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.