# Verifier service contract v1 (MINT-171, ADR-0003 §Decision 1/3/4).
#
# The transport boundary of the first-party service mode: TypeScript and
# PHP clients are transport-only consumers of THIS contract and never
# reimplement a check (SPEC-1 §14.4). Versioning follows ADR-0002 semver
# at the contract level: MAJOR if decision semantics or the reason-code
# vocabulary change, MINOR for additive surface, PATCH otherwise.
# Freeze process: gate D-s3 via bIP (docs/bip/bIP-0001, Proposed).
#
# Deliberately absent — these are invariants, never contract knobs:
#   * presentation lifetime (10 s constant, R14)
#   * any skip/lite flag for the nine acceptance conditions
#   * max_height_lag: LOCAL deployment policy (§4 of verifier-sdk.md),
#     configured at service start — exposing it per-request would let a
#     compromised web tier widen staleness acceptance.
openapi: 3.1.0
info:
  title: MintID Verifier Service
  description: >
    First-party service mode of the MintID Verifier SDK: embedded mode
    plus transport (verifier-sdk.md §2). Adds no authority (R26); every
    validation runs the same nine-condition pipeline as the library —
    there is no lite path. The service persists only the R18 decision
    record; presentations are never stored, forwarded, or logged.
  version: 1.1.0
  x-mintid:
    spec-revision: SPEC-1 v1.3
    freeze-gate: D-s3 (bIP-0001, pending)
    reason-codes-source: verifier_core.presentation REASON_* (verbatim)

paths:
  /v1/build-info:
    get:
      operationId: getBuildInfo
      summary: ADR-0002 release pins of the running artifact
      responses:
        "200":
          description: Pins, programmatically readable (never secrets).
          content:
            application/json:
              schema: { $ref: "#/components/schemas/BuildInfo" }

  /v1/challenges:
    post:
      operationId: createChallenge
      summary: Issue one signed §10.5 presentation challenge
      description: >
        Builds, shape-validates, and signs a challenge bound to this
        deployment's registered verifier identity (verifier_id, exact
        audience origin, request_key_id — deployment configuration, not
        request inputs). Nonce and session id are generated server-side;
        expiry is always now + 10 s (R14 constant); the finalized chain
        height is read from proven state at issuance.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ChallengeSpec" }
      responses:
        "201":
          description: The signed challenge to forward to the holder.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/IssuedChallenge" }
        "400":
          $ref: "#/components/responses/MalformedBody"
        "503":
          $ref: "#/components/responses/StateUnavailable"

  /v1/presentations:
    post:
      operationId: validatePresentation
      summary: Validate one holder presentation (the nine R14 conditions)
      description: >
        Runs verifier_core.validate_presentation verbatim. Every §10.5-
        parseable request yields HTTP 200 with a decision — accept or
        reject with a REASON_* code — and exactly one R18 decision record
        appended server-side. HTTP 400 is transport-level only (body not
        parseable into the §10.5 schema; no session identity exists, so
        no decision record is possible).
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ValidationInput" }
      responses:
        "200":
          description: The decision, reason codes identical to the library.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PresentationDecision" }
        "400":
          $ref: "#/components/responses/MalformedBody"

components:
  responses:
    MalformedBody:
      description: Body is not parseable into the documented schema.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/TransportError" }
    StateUnavailable:
      description: >
        Proven chain state is unavailable (light-client anchor or proven
        read failed). Fail-closed: no challenge is issued from unproven
        state (R15).
      content:
        application/json:
          schema: { $ref: "#/components/schemas/TransportError" }

  schemas:
    BuildInfo:
      type: object
      required: [sdk_version, engine_pin, state_vectors_version, state_vectors_sha256, spec_revision, conformance_suite_version]
      additionalProperties: false
      properties:
        sdk_version: { type: string }
        engine_pin: { type: string }
        state_vectors_version: { type: string }
        state_vectors_sha256: { type: string }
        spec_revision: { type: string }
        conformance_suite_version:
          type: string
          description: >
            §14.4 conformance-suite version this artifact passes (added
            in 1.1.0, the SDK-3 suite's first consumer-visible pin).

    ChallengeSpec:
      type: object
      required: [claim_policy]
      additionalProperties: false
      properties:
        issuer_policy: { $ref: "#/components/schemas/IssuerPolicy" }
        claim_policy:
          type: array
          minItems: 1
          items: { $ref: "#/components/schemas/ClaimPredicate" }
        same_credential_required: { type: boolean, default: true }

    IssuerPolicy:
      type: object
      additionalProperties: false
      properties:
        accepted_issuer_ids:
          type: array
          items: { $ref: "#/components/schemas/Hex32" }
        issuer_state_minimum:
          type: string
          enum: [ACTIVE, ENHANCED_MONITORING]
          default: ACTIVE

    ClaimPredicate:
      type: object
      required: [claim, value]
      additionalProperties: false
      properties:
        claim: { type: string }
        value:
          oneOf: [{ type: string }, { type: integer }, { type: boolean }]
        operator:
          type: string
          enum: [">=", "<=", "=="]

    PresentationRequestBody:
      type: object
      description: The §10.5 signed request body, schema-exact key names.
      required:
        [session_id, nonce, verifier_id, audience, request_key_id,
         expires_at, finalized_chain_height, issuer_policy, claim_policy,
         same_credential_required]
      additionalProperties: false
      properties:
        session_id: { type: string, pattern: "^[0-9a-f]{32}$" }
        nonce: { $ref: "#/components/schemas/Hex32" }
        verifier_id: { $ref: "#/components/schemas/Hex32" }
        audience: { type: string, description: Exact HTTPS origin (R24). }
        request_key_id: { $ref: "#/components/schemas/Hex32" }
        expires_at:
          type: integer
          description: Unix seconds; always issuance + 10 s (R14 constant).
        finalized_chain_height: { type: integer, minimum: 1 }
        issuer_policy: { $ref: "#/components/schemas/IssuerPolicy" }
        claim_policy:
          type: array
          minItems: 1
          items: { $ref: "#/components/schemas/ClaimPredicate" }
        same_credential_required: { type: boolean }

    IssuedChallenge:
      type: object
      required: [request, signature_hex]
      additionalProperties: false
      properties:
        request: { $ref: "#/components/schemas/PresentationRequestBody" }
        signature_hex:
          type: string
          description: >
            Signature over the canonical request body by the registered
            request key (holder-side verification semantics land with
            Epic F3 / MINT-48).

    PresentationEnvelope:
      type: object
      description: >
        The holder's response. Opaque proof bytes plus the public proof
        outputs — never holder attributes or PII (R2/R18).
      required: [proof_hex, issuer_id, status_root, claimed_predicates]
      additionalProperties: false
      properties:
        proof_hex: { type: string, pattern: "^([0-9a-f]{2})*$" }
        issuer_id: { $ref: "#/components/schemas/Hex32" }
        status_root: { $ref: "#/components/schemas/Hex32" }
        claimed_predicates:
          type: array
          items: { type: string }

    ValidationInput:
      type: object
      required: [request, envelope]
      additionalProperties: false
      properties:
        request: { $ref: "#/components/schemas/PresentationRequestBody" }
        envelope: { $ref: "#/components/schemas/PresentationEnvelope" }

    PresentationDecision:
      type: object
      required: [accepted, reason_code]
      additionalProperties: false
      properties:
        accepted: { type: boolean }
        reason_code: { $ref: "#/components/schemas/ReasonCode" }
        verified:
          oneOf:
            - { type: "null" }
            - $ref: "#/components/schemas/VerifiedPresentation"

    VerifiedPresentation:
      type: object
      required: [issuer_id, status_root, proven_predicates]
      additionalProperties: false
      properties:
        issuer_id: { $ref: "#/components/schemas/Hex32" }
        status_root: { $ref: "#/components/schemas/Hex32" }
        proven_predicates:
          type: array
          items: { type: string }

    ReasonCode:
      type: string
      description: >
        Closed vocabulary, byte-identical to what the library emits: the
        REASON_* constants plus the reason codes carried by the
        DomainValidationError hierarchy and PresentationProofError
        (conditions 1-2 and 5 reject with the error's own code). Shared
        verbatim with the SDK-3 cross-language conformance vectors. Any
        change is MAJOR.
      enum:
        - accepted
        - request_malformed
        - origin_invalid
        - origin_not_registered
        - verifier_not_active
        - request_key_not_active
        - revalidation_expired
        - domain_validation_failed
        - nonce_replayed
        - deadline_exceeded
        - proof_invalid
        - predicate_missing
        - issuer_not_accepted
        - issuer_not_active
        - status_root_stale
        - height_stale
        - state_unproven
        - nonce_race_lost

    TransportError:
      type: object
      required: [error]
      additionalProperties: false
      properties:
        error:
          type: string
          enum: [malformed_body, state_unavailable]

    Hex32:
      type: string
      pattern: "^[0-9a-f]{64}$"
      description: 32 bytes, lowercase hex.
