From f3d34590aa3f0968ec16bcf8821800b25a4aea0f Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Sun, 18 Oct 2026 13:30:00 +0200 Subject: [PATCH] docs(api): document existing insurance comparison contracts --- internal/api/openapi.yaml | 251 ++++++++++++++++++++++++++++ known_issues/38_openapi_contract.md | 19 +++ 2 files changed, 270 insertions(+) create mode 100644 known_issues/38_openapi_contract.md diff --git a/internal/api/openapi.yaml b/internal/api/openapi.yaml index 5ad33ce0..b6b38bd1 100644 --- a/internal/api/openapi.yaml +++ b/internal/api/openapi.yaml @@ -35,11 +35,81 @@ tags: - name: Health - name: Info - name: Docs + - name: Insurance + description: Read-only configuration status and configured plan comparison. # --------------------------------------------------------------------------- # Paths # --------------------------------------------------------------------------- paths: + /api/insurance/status: + get: + operationId: getInsuranceStatus + security: + - bearerAuth: [] + - apiKeyAuth: [] + tags: [Insurance] + summary: Check insurance comparison configuration + description: > + Requires view:recommendations and unrestricted account scope; scoped + sessions receive 404. Reports setting presence only, without resolving + secrets or contacting Archera. Missing contains setting names, never values. + responses: + '200': + description: Configuration presence; an unwired provider is unconfigured. + content: + application/json: + schema: {$ref: '#/components/schemas/InsuranceStatus'} + '401': {$ref: '#/components/responses/Unauthorized'} + '403': {$ref: '#/components/responses/Forbidden'} + '404': {$ref: '#/components/responses/NotFound'} + + /api/insurance/comparison: + get: + operationId: getInsuranceComparison + security: + - bearerAuth: [] + - apiKeyAuth: [] + tags: [Insurance] + summary: Read the configured plan's insurance comparison + description: > + Requires view:recommendations and unrestricted account scope; scoped + sessions receive 404. Disabled until ARCHERA_ORG_ID, ARCHERA_PLAN_ID and + ARCHERA_API_KEY_SECRET are present. Resolves the key lazily and requests + the configured plan without recommendation filters. This read-only result + does not purchase, bind insurance or change recommendation eligibility. + Vendor errors are mapped to fixed client messages, without implicit retries. + responses: + '200': + description: Plan-wide comparison with exact decimal strings and disclosures. + content: + application/json: + schema: {$ref: '#/components/schemas/ArcheraComparison'} + '401': {$ref: '#/components/responses/Unauthorized'} + '403': {$ref: '#/components/responses/Forbidden'} + '404': {$ref: '#/components/responses/NotFound'} + '429': + description: Vendor rate limit; Retry-After is present only for a positive vendor delay, rounded up to seconds. + headers: + Retry-After: + description: Seconds before retrying; no automatic retry is performed. + schema: {type: integer, minimum: 1} + content: + application/json: + schema: {$ref: '#/components/schemas/Error'} + '502': + description: > + Vendor authentication, missing organization/plan, service or transport + failure (including timeout), an inexact decimal, or a response over 5 MiB. + content: + application/json: + schema: {$ref: '#/components/schemas/Error'} + '503': + description: Missing configuration or the configured key could not be resolved. + content: + application/json: + schema: {$ref: '#/components/schemas/Error'} + # ---- Dashboard ---------------------------------------------------------- /api/dashboard/summary: get: @@ -2414,6 +2484,187 @@ components: # ---- Schemas ------------------------------------------------------------ schemas: + InsuranceStatus: + type: object + required: [missing, configured] + properties: + missing: + type: array + items: {type: string, enum: [ARCHERA_API_KEY_SECRET, ARCHERA_ORG_ID, ARCHERA_PLAN_ID]} + configured: {type: boolean} + + ArcheraDecimal: + type: string + nullable: true + pattern: '^-?[0-9]+(?:\.[0-9]+)?$' + description: Exact finite decimal, without rounding; null means unknown and differs from zero. + + ArcheraFinancials: + type: object + description: Monthly rates use a 730-hour basis; Archera premium is included in total commitment cost. + required: [monthly_730h_commitment_cost_total, monthly_730h_cloud_provider_cost, monthly_730h_archera_premium, monthly_730h_gross_savings, monthly_730h_net_savings, monthly_730h_covered_on_demand_cost] + properties: + monthly_730h_commitment_cost_total: {$ref: '#/components/schemas/ArcheraDecimal'} + monthly_730h_cloud_provider_cost: {$ref: '#/components/schemas/ArcheraDecimal'} + monthly_730h_archera_premium: {$ref: '#/components/schemas/ArcheraDecimal'} + monthly_730h_gross_savings: {$ref: '#/components/schemas/ArcheraDecimal'} + monthly_730h_net_savings: {$ref: '#/components/schemas/ArcheraDecimal'} + monthly_730h_covered_on_demand_cost: {$ref: '#/components/schemas/ArcheraDecimal'} + + ArcheraTotals: + allOf: + - {$ref: '#/components/schemas/ArcheraFinancials'} + - type: object + required: [upfront_one_time_cost] + properties: + upfront_one_time_cost: {$ref: '#/components/schemas/ArcheraDecimal'} + description: Flat monthly financial fields plus a separate one-time upfront amount. + + ArcheraProductSupport: + type: object + required: [status, underwriting_allowance, customer_eligibility] + properties: + status: {type: string} + source: {type: string} + evidence: {type: string} + underwriting_allowance: {type: string} + customer_eligibility: {type: string} + + ArcheraDelta: + type: object + required: [monthly_730h_net_savings, upfront_one_time_cost, discount_rate, breakeven_days] + properties: + monthly_730h_net_savings: {$ref: '#/components/schemas/ArcheraDecimal'} + upfront_one_time_cost: {$ref: '#/components/schemas/ArcheraDecimal'} + discount_rate: {$ref: '#/components/schemas/ArcheraDecimal'} + breakeven_days: {$ref: '#/components/schemas/ArcheraDecimal'} + + ArcheraHypotheticalDelta: + type: object + required: [monthly_730h_net_savings, monthly_730h_commitment_cost, upfront_one_time_cost] + properties: + monthly_730h_net_savings: {$ref: '#/components/schemas/ArcheraDecimal'} + monthly_730h_commitment_cost: {$ref: '#/components/schemas/ArcheraDecimal'} + upfront_one_time_cost: {$ref: '#/components/schemas/ArcheraDecimal'} + + ArcheraLineItem: + type: object + required: [line_item_id, reason, actual_term, actual_payment_option, actual_commitment_type] + properties: + line_item_id: {type: string} + reason: {type: string} + actual_term: {type: string, nullable: true} + actual_payment_option: {type: string, nullable: true} + actual_commitment_type: {type: string, nullable: true} + + ArcheraHypothetical: + type: object + required: [contract_term, payment_option, totals, delta_vs_current, line_items] + properties: + contract_term: {type: string, nullable: true} + payment_option: {type: string} + totals: {$ref: '#/components/schemas/ArcheraTotals'} + delta_vs_current: {$ref: '#/components/schemas/ArcheraHypotheticalDelta'} + line_items: {type: array, items: {$ref: '#/components/schemas/ArcheraLineItem'}} + + ArcheraOffer: + type: object + required: [offer_id, is_current, provider, commitment_type, region, contract_term, payment_option, lease_attached, lease_menu_item_id, archera_product_support, discount_rate, breakeven_days, monthly, upfront_one_time_cost, delta_vs_current] + properties: + offer_id: {type: string} + is_current: {type: boolean} + provider: {type: string} + commitment_type: {type: string} + region: {type: string, nullable: true} + contract_term: {type: string, nullable: true} + payment_option: {type: string, nullable: true} + lease_attached: {type: boolean} + lease_menu_item_id: {type: string, nullable: true} + archera_offer_name: {type: string, nullable: true} + archera_offer_name_note: {type: string} + archera_product_support: {$ref: '#/components/schemas/ArcheraProductSupport'} + discount_rate: {$ref: '#/components/schemas/ArcheraDecimal'} + breakeven_days: {$ref: '#/components/schemas/ArcheraDecimal'} + monthly: {$ref: '#/components/schemas/ArcheraFinancials'} + upfront_one_time_cost: {$ref: '#/components/schemas/ArcheraDecimal'} + delta_vs_current: {$ref: '#/components/schemas/ArcheraDelta'} + + ArcheraRow: + type: object + required: [line_item_id, current, candidates] + properties: + line_item_id: {type: string} + current: {$ref: '#/components/schemas/ArcheraOffer'} + candidates: {type: array, items: {$ref: '#/components/schemas/ArcheraOffer'}} + + ArcheraComparison: + type: object + required: [title, plan_id, fetched_at, currency, currency_note, premium_included, basis_note, delta_basis_note, current, hypotheticals, rows, non_gating_disclosure, sponsorship_disclosure] + properties: + title: {type: string} + plan_id: {type: string} + fetched_at: {type: string, format: date-time} + currency: {type: string, nullable: true, enum: [null]} + currency_note: {type: string} + premium_included: {type: boolean} + basis_note: {type: string} + delta_basis_note: {type: string} + current: {$ref: '#/components/schemas/ArcheraTotals'} + hypotheticals: {type: array, items: {$ref: '#/components/schemas/ArcheraHypothetical'}} + rows: {type: array, items: {$ref: '#/components/schemas/ArcheraRow'}} + non_gating_disclosure: {type: string} + sponsorship_disclosure: {type: string} + + ArcheraRecommendationComparison: + type: object + description: Existing internal recommendation DTO contract; no HTTP operation is advertised here. Resource association does not establish comparable economics. + required: [recommendation_id, status, reason, amount_provenance, economic_status, fetched_at, currency, currency_note, comparable_delta, premium_included, current, candidates, non_gating_disclosure, sponsorship_disclosure] + properties: + recommendation_id: {type: string} + status: {type: string} + reason: {type: string} + amount_provenance: {type: string} + economic_status: {type: string} + fetched_at: {type: string, format: date-time} + currency: {type: string, nullable: true, enum: [null]} + currency_note: {type: string} + comparable_delta: {type: string, nullable: true, enum: [null]} + premium_included: {type: boolean} + current: {$ref: '#/components/schemas/ArcheraRecommendationOffer'} + candidates: {type: array, items: {$ref: '#/components/schemas/ArcheraRecommendationAlternative'}} + non_gating_disclosure: {type: string} + sponsorship_disclosure: {type: string} + + ArcheraRecommendationOffer: + type: object + nullable: true + description: Existing internal offer DTO; native duration is seconds. Candidate native identity stays null rather than inheriting current identity. Upfront cost is one-time; currency is not inferred. + required: [offer_id, provider, commitment_type, hourly_selected_commitment, native_duration_seconds, native_plan_type, native_instance_family, effective_contract_term, payment_option, lease_attached, monthly, upfront_one_time_cost] + properties: + offer_id: {type: string} + provider: {type: string} + commitment_type: {type: string} + hourly_selected_commitment: {$ref: '#/components/schemas/ArcheraDecimal'} + native_duration_seconds: {$ref: '#/components/schemas/ArcheraDecimal'} + native_plan_type: {type: string, nullable: true} + native_instance_family: {type: string, nullable: true} + effective_contract_term: {type: string, nullable: true} + payment_option: {type: string, nullable: true} + lease_attached: {type: boolean} + monthly: {$ref: '#/components/schemas/ArcheraFinancials'} + upfront_one_time_cost: {$ref: '#/components/schemas/ArcheraDecimal'} + + ArcheraRecommendationAlternative: + type: object + description: Existing internal candidate DTO; a candidate can have null figures when its basis is unproven. + required: [offer_id, status, reason, native_identity_status, figures] + properties: + offer_id: {type: string} + status: {type: string} + reason: {type: string} + native_identity_status: {type: string} + figures: {$ref: '#/components/schemas/ArcheraRecommendationOffer'} + # -- Azure RI exchange -------------------------------------------------- AzureExchangeSource: type: object diff --git a/known_issues/38_openapi_contract.md b/known_issues/38_openapi_contract.md new file mode 100644 index 00000000..076cb82c --- /dev/null +++ b/known_issues/38_openapi_contract.md @@ -0,0 +1,19 @@ +# Known Issues: OpenAPI dialect validation + +> **Audit status (2026-10-18):** `1 needs triage ยท 0 resolved` + +## LOW: two existing schemas fail OpenAPI 3.0.3 structural validation + +**Files:** `internal/api/openapi.yaml` + +**Description:** `RecommendationsFreshness.last_collection_partial` uses +`nullable` without an explicit type. `PlanRequest.max_step_commitment` uses +a numeric `exclusiveMinimum`, while the declared 3.0.3 dialect requires a +boolean. The baseline validator reports two errors and 16 warnings. These +errors prevent claiming spec-wide structural validity; they are separate +from the insurance contract documentation. + +**Why deferred:** dialect corrections affect unrelated contracts and are +tracked separately to keep the insurance documentation change focused. + +**Status:** Open. Tracked in #1075.