openapi: 3.1.0
info:
  title: UAS API
  version: 0.1.0-draft
  description: |
    User Account Service (UAS).

    Contract notes:
    - API Gateway base path: `/uas`
    - API Gateway exposes only `POST /stat`.
    - API Gateway invocation of `POST /stat` requires `email` + `passcode` (no `user_id`-only capability-token path).
    - Email inputs are canonicalized (trim + lowercase) before uniqueness checks and lookups.
    - Optional cost attribution: callers may provide `cccode` as a request field and/or header `x-cccode`. Format `^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$`, canonicalized to uppercase; invalid values (or body/header mismatch) return HTTP 400.
    - Invalid credentials return `401` without disclosing whether the email exists (anti-enumeration).
    - Direct Lambda invocation exists (CLI/internal onboarding) but is not covered by this OpenAPI file.
    - `session_guid` is treated as a bearer secret and is never emitted; use `session_fingerprint` for correlation.
  license:
    name: Proprietary
    url: https://g3nretailstack.com/license
servers:
  - url: https://api.g3nretailstack.com/uas
components:
  schemas:
    BuildMeta:
      type: object
      properties:
        build_major: { type: string }
        build_minor: { type: string }
        build_id: { type: string }
      required: [build_major, build_minor, build_id]
    Stats:
      type: object
      properties:
        call: { type: string }
        service: { type: string, enum: [uas] }
        request_id: { type: string }
        timestamp_utc: { type: string, format: date-time }
        build: { $ref: '#/components/schemas/BuildMeta' }
        latency_ms: { type: integer }
        bandwidth_in_bytes: { type: integer }
        bandwidth_out_bytes: { type: integer }
        other_service_calls: { type: integer }
        actor: { type: string }
        session_fingerprint:
          type: string
          description: Non-reversible SHA-256 fingerprint of a caller-provided `session_guid` (never an auth credential).
        user_guid: { type: string }
        orgcode: { type: string }
        cccode: { type: string }
      required: [call, service, timestamp_utc, build]
    ErrorMessage:
      type: object
      properties:
        en_US: { type: string }
      required: [en_US]
    ErrorTag:
      type: object
      properties:
        tag: { type: string }
        message: { $ref: '#/components/schemas/ErrorMessage' }
      required: [tag, message]
    Error:
      type: object
      properties:
        error_code: { type: string }
        http_status: { type: integer }
        retryable: { type: boolean }
        request_id: { type: string }
        trace_id: { type: string }
        major: { $ref: '#/components/schemas/ErrorTag' }
        minor: { $ref: '#/components/schemas/ErrorTag' }
        details: {}
      required: [major]
    Envelope:
      type: object
      properties:
        success: { type: boolean }
        data: {}
        stats: { $ref: '#/components/schemas/Stats' }
        error: { $ref: '#/components/schemas/Error' }
        revision:
          type: string
          description: User record revision (not call-level).
      required: [success]
    ErrorResponse:
      allOf:
        - $ref: '#/components/schemas/Envelope'
        - type: object
          properties:
            success: { type: boolean, const: false }
          required: [success, error, stats]
    OkResponse:
      allOf:
        - $ref: '#/components/schemas/Envelope'
        - type: object
          properties:
            success: { type: boolean, const: true }
            data: {}
          required: [success, data, stats]
    UasStatRequest:
      type: object
      properties:
        email: { type: string, format: email }
        passcode: { type: string }
        include_payment_history:
          type: boolean
          description: When true, includes `status_history` for payment methods.
        actor: { type: string }
        orgcode: { type: string }
        cccode:
          type: string
          pattern: '^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$'
          description: Optional cost attribution (canonical uppercase). May also be provided via header `x-cccode`.
      required: [email, passcode]
    EmailRecord:
      type: object
      properties:
        email: { type: string, format: email }
        status: { type: string }
        is_primary: { type: boolean }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        doomed_at: { type: string, format: date-time }
      required: [email, status, is_primary, created_at, updated_at]
    PasscodeSnapshot:
      type: object
      properties:
        set: { type: boolean }
        updated_at: { type: string, format: date-time, nullable: true }
      required: [set]
    PaymentMethodStatusHistory:
      type: object
      properties:
        status: { type: string }
        reason: { type: string }
        changed_at: { type: string, format: date-time }
      required: [status, reason, changed_at]
    PaymentMethodRecord:
      type: object
      properties:
        pm_id: { type: string }
        processor: { type: string }
        token_type: { type: string }
        status: { type: string }
        priority: { type: integer }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        doomed_at: { type: string, format: date-time }
        default: { type: boolean }
        status_history:
          type: array
          items: { $ref: '#/components/schemas/PaymentMethodStatusHistory' }
      required: [pm_id, processor, token_type, status, priority, created_at, updated_at]
    UserSnapshot:
      type: object
      properties:
        user_id: { type: string }
        account_ref: { type: string }
        status: { type: string }
        caption: { type: string }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        emails:
          type: array
          items: { $ref: '#/components/schemas/EmailRecord' }
        passcode: { $ref: '#/components/schemas/PasscodeSnapshot' }
        payment_methods:
          type: array
          items: { $ref: '#/components/schemas/PaymentMethodRecord' }
        pm_default: { type: string }
      required: [user_id, account_ref, status, caption, created_at, updated_at, emails, passcode, payment_methods]
    UasStatSuccess:
      allOf:
        - $ref: '#/components/schemas/OkResponse'
        - type: object
          properties:
            data: { $ref: '#/components/schemas/UserSnapshot' }
          required: [data, revision]
paths:
  /stat:
    post:
      operationId: uasStat
      summary: Fetch a user snapshot (email + passcode)
      x-route-class: Tier B
      x-qps-target: 300
      x-concurrency-target: 500
      x-latency-p95-ms: 300
      description: |
        Returns a user snapshot envelope.

        Notes:
        - Requires `email` + `passcode` (API Gateway).
        - On success, `revision` is the current revision of the user record.
        - `session_guid` is not accepted/required on this surface and is never emitted.
        - Invalid credentials return `401` without disclosing whether the email exists.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/UasStatRequest' }
            examples:
              basic:
                value:
                  email: user@example.com
                  passcode: Passw0rd!
              include_payment_history:
                value:
                  email: user@example.com
                  passcode: Passw0rd!
                  include_payment_history: true
      responses:
        '200':
          description: User snapshot retrieved
          content:
            application/json:
              schema: { $ref: '#/components/schemas/UasStatSuccess' }
        '400':
          description: Validation error (missing credentials)
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ErrorResponse' }
        '401':
          description: Invalid credentials
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ErrorResponse' }
        '500':
          description: Internal error
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ErrorResponse' }
