openapi: 3.0.3
info:
  title: flag-store
  version: 0.1.0
  description: >
    Сервис фича-флагов. Булевы флаги по окружениям, правила по атрибутам пользователя,
    детерминированный процентный rollout (SHA-256 от флага и id пользователя, корзины 0..9999),
    журнал аудита и SSE-поток изменений. Хранилище SQLite.
servers:
  - url: http://localhost:8114
paths:
  /v1/info:
    get:
      summary: Список окружений и режим авторизации записи
      responses:
        "200": { description: ok }
  /v1/flags:
    get:
      summary: Все флаги с конфигурацией по окружениям
      responses:
        "200": { description: Список флагов }
  /v1/flags/{key}:
    parameters:
      - { name: key, in: path, required: true, schema: { type: string, pattern: "^[a-z0-9][a-z0-9._-]{0,63}$" } }
    get:
      summary: Один флаг
      responses:
        "200": { description: Флаг }
        "404": { description: Не найден }
    put:
      summary: Создать флаг или изменить описание
      security: [{ bearer: [] }]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                description: { type: string, maxLength: 300 }
      responses:
        "201": { description: Создан }
        "200": { description: Обновлён }
        "400": { description: Некорректный ввод }
        "401": { description: Нужен токен (если задан API_TOKEN) }
    delete:
      summary: Удалить флаг со всеми окружениями
      security: [{ bearer: [] }]
      responses:
        "204": { description: Удалён }
        "404": { description: Не найден }
  /v1/flags/{key}/envs/{env}:
    put:
      summary: Задать конфигурацию окружения (версия растёт на 1)
      security: [{ bearer: [] }]
      parameters:
        - { name: key, in: path, required: true, schema: { type: string } }
        - { name: env, in: path, required: true, schema: { type: string, example: prod } }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/EnvConfigInput" }
      responses:
        "200": { description: Новая конфигурация }
        "400": { description: Некорректный ввод }
        "404": { description: Флага нет }
  /v1/evaluate:
    post:
      summary: Вычислить флаг для пользователя
      description: "Порядок: выключено, первое сработавшее правило, затем rollout. Открытый эндпоинт."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [env, flag, user]
              properties:
                env: { type: string }
                flag: { type: string }
                user:
                  type: object
                  required: [id]
                  properties:
                    id: { type: string }
                    attrs: { type: object, additionalProperties: true }
      responses:
        "200":
          description: Результат
          content:
            application/json:
              schema:
                type: object
                properties:
                  enabled: { type: boolean }
                  reason: { type: string, description: "disabled | rule:N | rollout | not_in_rollout" }
                  version: { type: integer }
                  bucket: { type: integer, description: "Корзина пользователя 0..9999" }
        "404": { description: Флага нет }
  /v1/audit:
    get:
      summary: Журнал изменений (новые сверху)
      parameters:
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 500, default: 50 } }
        - { name: flag, in: query, schema: { type: string } }
      responses:
        "200": { description: Записи аудита }
  /v1/stream:
    get:
      summary: SSE-поток изменений окружения
      description: "События: hello (при подключении), change (изменение), resync (потеряны события, перечитайте флаги)."
      parameters:
        - { name: env, in: query, required: true, schema: { type: string } }
      responses:
        "200":
          description: text/event-stream
          content:
            text/event-stream: { schema: { type: string } }
  /healthz:
    get:
      summary: Проверка живости
      responses:
        "200": { description: ok }
components:
  securitySchemes:
    bearer: { type: http, scheme: bearer }
  schemas:
    EnvConfigInput:
      type: object
      required: [enabled]
      properties:
        enabled: { type: boolean }
        rollout_bp: { type: integer, minimum: 0, maximum: 10000, description: "Доля в базисных пунктах (2500 = 25%)" }
        rules:
          type: array
          maxItems: 20
          items:
            type: object
            required: [attr, op, value, serve]
            properties:
              attr: { type: string, description: "Имя атрибута; id это идентификатор пользователя" }
              op: { type: string, enum: [eq, neq, in, contains, starts_with, gt, lt] }
              value: {}
              serve: { type: boolean }
