openapi: 3.0.3
info:
  title: GNX Earsling Range OS Engine API
  version: 1.0.0
  description: |
    Normal-range candidate policy OS engine.
    The engine does not create measurement values, diagnose disease, prescribe treatment, or expose raw health values.
servers:
  - url: https://earsling.com
paths:
  /health/ready:
    get:
      summary: Engine readiness gate
      responses:
        "200":
          description: Ready state with policy lock and fail-closed status
  /v1/profile/normalize:
    post:
      summary: Normalize profile into non-raw profile bands
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProfileNormalizeRequest"
      responses:
        "200":
          description: Normalized profile
  /v1/range/evaluate:
    post:
      summary: Evaluate candidate state against locked normal-range policy
      description: Does not persist Evidence Packet unless persistEvidence=true.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EvaluateRequest"
      responses:
        "200":
          description: Rule Graph evaluation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EvaluationResponse"
  /v1/events/out-of-range:
    post:
      summary: Create Evidence Packet for an out-of-range candidate event
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EvaluateRequest"
      responses:
        "200":
          description: Evaluation with evidence packet and audit receipt
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EvaluationResponse"
  /v1/evidence/{id}:
    get:
      summary: Fetch Evidence Packet
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Evidence packet
  /v1/replay/{id}:
    get:
      summary: Replay Evidence Packet and compare decision
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Replay result
  /v1/adapter/mobile/preview:
    post:
      summary: Preview Android/iOS adapter payload
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EvaluateRequest"
      responses:
        "200":
          description: Mobile adapter contract payload
  /v1/docs/manifest:
    get:
      summary: Public document manifest
      responses:
        "200":
          description: Docs manifest
  /v1/sdk/manifest:
    get:
      summary: SDK/API contract manifest
      responses:
        "200":
          description: SDK/API contract manifest
components:
  schemas:
    ProfileNormalizeRequest:
      type: object
      properties:
        profile:
          $ref: "#/components/schemas/ProfileInput"
    ProfileInput:
      type: object
      required: [age, sex, heightCm, weightKg]
      properties:
        age: { type: integer, minimum: 18, maximum: 100 }
        sex: { type: string, enum: [female, male, other, undisclosed] }
        heightCm: { type: number }
        weightKg: { type: number }
        diabetesYn: { type: string, enum: [Y, N, unknown] }
        consentScope: { type: string, enum: [demo, evaluation] }
    CandidateState:
      type: object
      required: [metric]
      properties:
        metric: { type: string, enum: [body_temperature, heart_rhythm, blood_pressure_range] }
        thermalBand: { type: string, enum: [normal, outside_low, outside_high] }
        ambientThermalBand: { type: string, enum: [normal, low, high] }
        heartRateBand: { type: string, enum: [normal, outside_low, outside_high] }
        rhythmIrregularityBand: { type: string, enum: [normal, candidate_irregular, outside] }
        bloodPressureBand: { type: string, enum: [normal, elevated, outside_high, severe] }
        candidateSource: { type: string, enum: [demo_adapter, sdk_adapter, ble_adapter, cloud_adapter] }
    ContextState:
      type: object
      required: [contactQuality, frameStable, baselineReady, motionNoise, adapterIntegrity, sourceIntegrity]
      properties:
        contactQuality: { type: string, enum: [excellent, good, fair, poor] }
        frameStable: { type: boolean }
        baselineReady: { type: boolean }
        motionNoise: { type: string, enum: [low, medium, high] }
        adapterIntegrity: { type: string, enum: [verified, unverified, fail] }
        sourceIntegrity: { type: string, enum: [verified, unverified, fail] }
    EvaluateRequest:
      type: object
      required: [profile, candidateState, contextState]
      properties:
        profile:
          $ref: "#/components/schemas/ProfileInput"
        candidateState:
          $ref: "#/components/schemas/CandidateState"
        contextState:
          $ref: "#/components/schemas/ContextState"
        persistEvidence:
          type: boolean
    EvaluationResponse:
      type: object
      required: [ok, engine, engineVersion, policyVersion, ruleGraphVersion, metric, decision, message, triggeredRules, uiDirectives, adapterPayload, disclaimers, nonClaims]
      properties:
        ok: { type: boolean }
        engine: { type: string, enum: [gnx-earsling-range-os] }
        engineVersion: { type: string }
        policyVersion: { type: string }
        ruleGraphVersion: { type: string }
        metric: { type: string }
        decision: { type: string, enum: [GREEN_NORMAL, AMBER_CHECK, ORANGE_OUT_OF_RANGE, RED_SEVERE_RECHECK, GRAY_CONTEXT_INVALID] }
        message: { type: string }
        evidencePacketId: { type: string }
        auditReceipt: { type: string }
        triggeredRules:
          type: array
          items:
            type: object
        uiDirectives:
          type: object
        adapterPayload:
          type: object
        disclaimers:
          type: array
          items: { type: string }
        nonClaims:
          type: array
          items: { type: string }
