Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
251 changes: 251 additions & 0 deletions internal/api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions known_issues/38_openapi_contract.md
Original file line number Diff line number Diff line change
@@ -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.
Loading