openapi: 3.1.0
info:
  title: AIPhone No・where Logicnoid API
  version: 0.1.0
  summary: Phone-number non-exposure display/session interlock engine
  description: AIPhone No・where Logicnoid Project provides text-identifier communication identity, no-raw-MSISDN tokenization, Display Ready Proof, one-time Tunnel Ticket, one-time session authorization, audit receipt retrieval, and signed admin challenge verification.
servers:
  - url: https://logicnoid.kr
    description: Primary production-near public surface
  - url: https://www.logicnoid.kr
    description: WWW public surface
  - url: https://origin.logicnoid.kr
    description: Origin surface

tags:
  - name: Health
  - name: Identity
  - name: Session Interlock
  - name: Audit
  - name: Admin

paths:
  /health/ready:
    get:
      tags: [Health]
      summary: Runtime readiness check
      operationId: getRuntimeReady
      responses:
        "200":
          description: Runtime is ready
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthReadyResponse"

  /v1/msisdn/tokenize:
    post:
      tags: [Identity]
      summary: Tokenize MSISDN without storing raw phone number
      operationId: tokenizeMsisdn
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MsisdnTokenizeRequest"
      responses:
        "200":
          description: Token fingerprint created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MsisdnTokenizeResponse"

  /v1/identity/claim:
    post:
      tags: [Identity]
      summary: Claim a text identifier mapped to a non-reversible token fingerprint
      operationId: claimIdentity
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/IdentityClaimRequest"
      responses:
        "201":
          description: Text identifier claimed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IdentityClaimResponse"
        "409":
          description: Text identifier already claimed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/identity/resolve:
    post:
      tags: [Identity]
      summary: Resolve a text identifier
      operationId: resolveIdentity
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/IdentityResolveRequest"
      responses:
        "200":
          description: Resolve result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IdentityResolveResponse"

  /v1/session/intent:
    post:
      tags: [Session Interlock]
      summary: Create a communication session intent
      operationId: createSessionIntent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SessionIntentRequest"
      responses:
        "201":
          description: Session intent created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionIntentResponse"

  /v1/display/ready:
    post:
      tags: [Session Interlock]
      summary: Submit Display Ready Proof for a session intent
      operationId: submitDisplayReady
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DisplayReadyRequest"
      responses:
        "201":
          description: Display Ready Proof created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DisplayReadyResponse"

  /v1/tunnel/ticket:
    post:
      tags: [Session Interlock]
      summary: Issue a one-time opaque Tunnel Ticket after Display Ready Proof
      operationId: issueTunnelTicket
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TunnelTicketRequest"
      responses:
        "201":
          description: Tunnel Ticket issued
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TunnelTicketResponse"

  /v1/session/authorize:
    post:
      tags: [Session Interlock]
      summary: Authorize a session using a one-time Tunnel Ticket
      operationId: authorizeSession
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SessionAuthorizeRequest"
      responses:
        "201":
          description: Session authorization issued
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionAuthorizeResponse"

  /v1/session/consume:
    post:
      tags: [Session Interlock]
      summary: Consume a one-time session authorization
      operationId: consumeSessionAuthorization
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SessionConsumeRequest"
      responses:
        "200":
          description: Session allowed once
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionConsumeResponse"
        "409":
          description: Session authorization already consumed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/audit/{receipt}:
    get:
      tags: [Audit]
      summary: Retrieve an audit receipt
      operationId: getAuditReceipt
      parameters:
        - name: receipt
          in: path
          required: true
          schema:
            type: string
            pattern: "^[a-f0-9]{64}$"
      responses:
        "200":
          description: Audit receipt found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuditReceiptResponse"
        "404":
          description: Audit receipt not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/admin/challenge:
    post:
      tags: [Admin]
      summary: Issue signed admin challenge
      operationId: issueAdminChallenge
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AdminChallengeRequest"
      responses:
        "201":
          description: Admin challenge issued
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminChallengeResponse"

  /v1/admin/session/verify:
    post:
      tags: [Admin]
      summary: Verify admin challenge signature and issue admin session
      operationId: verifyAdminSession
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AdminSessionVerifyRequest"
      responses:
        "201":
          description: Admin session issued
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminSessionVerifyResponse"

  /v1/admin/readiness:
    get:
      tags: [Admin]
      summary: Protected admin readiness view
      operationId: getAdminReadiness
      security:
        - bearerAdminSession: []
      responses:
        "200":
          description: Protected readiness view
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminReadinessResponse"
        "401":
          description: Admin session required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

components:
  securitySchemes:
    bearerAdminSession:
      type: http
      scheme: bearer
      bearerFormat: opaque-admin-session-handle

  schemas:
    HealthReadyResponse:
      type: object
      required: [ok, status, product, engine, checks, time]
      properties:
        ok:
          type: boolean
        status:
          type: string
          enum: [READY, NOT_READY]
        product:
          type: string
        engine:
          type: string
        checks:
          type: object
        time:
          type: string
          format: date-time

    MsisdnTokenizeRequest:
      type: object
      required: [msisdn]
      properties:
        msisdn:
          type: string
          example: "+821012345678"

    MsisdnTokenizeResponse:
      type: object
      required: [ok, tokenFingerprint, rawMsisdnStored, receipt]
      properties:
        ok:
          type: boolean
        tokenFingerprint:
          type: string
        rawMsisdnStored:
          type: boolean
          enum: [false]
        receipt:
          type: string

    IdentityClaimRequest:
      type: object
      required: [textIdentifier, msisdn]
      properties:
        textIdentifier:
          type: string
          example: "gnx-aiphone"
        msisdn:
          type: string
          example: "+821012345678"

    IdentityClaimResponse:
      type: object
      required: [ok, identityId, textIdentifier, tokenFingerprint, rawMsisdnStored, receipt]
      properties:
        ok:
          type: boolean
        identityId:
          type: string
        textIdentifier:
          type: string
        tokenFingerprint:
          type: string
        rawMsisdnStored:
          type: boolean
          enum: [false]
        receipt:
          type: string

    IdentityResolveRequest:
      type: object
      required: [textIdentifier]
      properties:
        textIdentifier:
          type: string

    IdentityResolveResponse:
      type: object
      required: [ok, exists, identity, receipt]
      properties:
        ok:
          type: boolean
        exists:
          type: boolean
        identity:
          type:
            - object
            - "null"
        receipt:
          type: string

    SessionIntentRequest:
      type: object
      required: [callerTextIdentifier, calleeTextIdentifier]
      properties:
        callerTextIdentifier:
          type: string
        calleeTextIdentifier:
          type: string

    SessionIntentResponse:
      type: object
      required: [ok, sessionIntentId, status, expiresInSeconds, receipt]
      properties:
        ok:
          type: boolean
        sessionIntentId:
          type: string
        status:
          type: string
          enum: [PENDING_DISPLAY_READY]
        expiresInSeconds:
          type: integer
        receipt:
          type: string

    DisplayReadyRequest:
      type: object
      required: [sessionIntentId, calleeTextIdentifier, deviceId]
      properties:
        sessionIntentId:
          type: string
        calleeTextIdentifier:
          type: string
        deviceId:
          type: string

    DisplayReadyResponse:
      type: object
      required: [ok, displayProofId, sessionIntentId, status, expiresInSeconds, receipt]
      properties:
        ok:
          type: boolean
        displayProofId:
          type: string
        sessionIntentId:
          type: string
        status:
          type: string
          enum: [DISPLAY_READY]
        expiresInSeconds:
          type: integer
        receipt:
          type: string

    TunnelTicketRequest:
      type: object
      required: [sessionIntentId, displayProofId]
      properties:
        sessionIntentId:
          type: string
        displayProofId:
          type: string

    TunnelTicketResponse:
      type: object
      required: [ok, sessionIntentId, tunnelTicketHandle, serverStoresRawTicket, expiresInSeconds, receipt]
      properties:
        ok:
          type: boolean
        sessionIntentId:
          type: string
        tunnelTicketHandle:
          type: string
        serverStoresRawTicket:
          type: boolean
          enum: [false]
        expiresInSeconds:
          type: integer
        receipt:
          type: string

    SessionAuthorizeRequest:
      type: object
      required: [sessionIntentId, tunnelTicketHandle]
      properties:
        sessionIntentId:
          type: string
        tunnelTicketHandle:
          type: string

    SessionAuthorizeResponse:
      type: object
      required: [ok, sessionIntentId, sessionAuthorizationHandle, status, expiresInSeconds, receipt]
      properties:
        ok:
          type: boolean
        sessionIntentId:
          type: string
        sessionAuthorizationHandle:
          type: string
        status:
          type: string
          enum: [AUTHORIZED_PENDING_CONSUME]
        expiresInSeconds:
          type: integer
        receipt:
          type: string

    SessionConsumeRequest:
      type: object
      required: [sessionAuthorizationHandle]
      properties:
        sessionAuthorizationHandle:
          type: string

    SessionConsumeResponse:
      type: object
      required: [ok, sessionIntentId, result, receipt]
      properties:
        ok:
          type: boolean
        sessionIntentId:
          type: string
        result:
          type: string
          enum: [SESSION_ALLOWED_ONCE]
        receipt:
          type: string

    AuditReceiptResponse:
      type: object
      required: [ok, audit]
      properties:
        ok:
          type: boolean
        audit:
          type: object

    AdminChallengeRequest:
      type: object
      properties:
        adminId:
          type: string
          example: "gnx-admin"

    AdminChallengeResponse:
      type: object
      required: [ok, adminChallengeId, nonce, message, signatureAlgorithm, expiresInSeconds, receipt]
      properties:
        ok:
          type: boolean
        adminChallengeId:
          type: string
        nonce:
          type: string
        message:
          type: string
        signatureAlgorithm:
          type: string
          enum: [HMAC-SHA256]
        expiresInSeconds:
          type: integer
        receipt:
          type: string

    AdminSessionVerifyRequest:
      type: object
      required: [adminChallengeId, nonce, signature]
      properties:
        adminChallengeId:
          type: string
        nonce:
          type: string
        signature:
          type: string
          pattern: "^[a-f0-9]{64}$"

    AdminSessionVerifyResponse:
      type: object
      required: [ok, adminSessionHandle, serverStoresRawAdminSession, expiresInSeconds, receipt]
      properties:
        ok:
          type: boolean
        adminSessionHandle:
          type: string
        serverStoresRawAdminSession:
          type: boolean
          enum: [false]
        expiresInSeconds:
          type: integer
        receipt:
          type: string

    AdminReadinessResponse:
      type: object
      required: [ok, product, admin, gates, counts, receipt]
      properties:
        ok:
          type: boolean
        product:
          type: string
        admin:
          type: object
        gates:
          type: object
        counts:
          type: object
        receipt:
          type: string

    ErrorResponse:
      type: object
      required: [ok, code, message, receipt]
      properties:
        ok:
          type: boolean
          enum: [false]
        code:
          type: string
        message:
          type: string
        receipt:
          type: string
