FSP 1.0

FSP / Манифест

Манифест и согласование

Сторона FSP не договаривается о формате в переписке. Она публикует манифест по адресу /.well-known/fsp, где машиночитаемо сказано: какие модули поддерживаю, каких версий, на каком уровне и с какими расширениями. Первый шаг любого обмена — забрать манифест другой стороны и свести его со своим.

Схема манифеста — fsp-profile.schema.json. Правила согласования нормативны и лежат в FSP Core под кодами PROFILE-001PROFILE-008 (modules/core/rules.csv).

Зачем

Без манифеста участие в обмене означает «быть заведённым в чужой админке». С манифестом оператор становится участником, отдав ссылку: его прайс и мощности читает любая площадка, а не одна. Это же снимает вопрос «почему не сработало»: если модуль или версия не совпали, это видно сразу.

Кто что объявляет

Роль Обычно объявляет Смысл
operator core, pricing, capacity Отдаёт свой прайс и свободные мощности
requester core, quote Заявитель или его агент: присылает ТЗ и объём, читает сметы
aggregator core, pricing (read_only), capacity (read_only), quote Площадка: читает операторов, отдаёт сметы заявителям

read_only: true означает «умею читать данные модуля, но своих не отдаю». Площадке не нужен собственный прайс, чтобы считать по чужому.

Как сводятся два манифеста

Считает принимающая сторона, её результат окончателен (PROFILE-005).

  1. Кандидаты — только модули, объявленные обеими сторонами (PROFILE-003).
  2. Для каждого кандидата берётся пересечение versions, выбирается наибольшая. Пустое пересечение исключает модуль.
  3. Модуль исключается, если исключена любая его зависимость. Расширение исключается вместе с родительским модулем. Пересчёт повторяется, пока набор не перестанет меняться (PROFILE-004).
  4. У модуля с уровнями соответствия согласованный уровень — наименьший общий (PROFILE-006).
  5. Незнакомые поля, модули и расширения игнорируются, а не роняют обмен (PROFILE-008).

Пример

Площадка МПФИТ (core 1.0, pricing 1.0 L3 read_only, capacity 1.0 read_only, quote 1.0) сводится с оператором «Склад 24» (core 1.0, pricing 1.0 L1, capacity 1.0):

core      1.0        активен
pricing   1.0  L1    активен, уровень понижен до L1 — у оператора плоский прайс
capacity  1.0        активен
quote     —          неактивен: оператор его не объявлял, сметы считает площадка

Дальше запрос с pricing_level_min: "L2" не выкинет «Склад 24» из выдачи. Он попадёт туда с fit: partial и кодом pricing_level_insufficient, а в кабинете оператора это превратится в понятную задачу: описать условия и подняться до L2.

Расширения

Своя операция, которой нет в стандарте, не повод ждать новой версии. Расширение объявляется в манифесте под именем x-<vendor>:<code> с указанием родительского модуля:

"x-sklad:route-cutoff-fee": {
  "module": "pricing",
  "versions": ["1.0"],
  "spec": "https://sklad.example/fsp/route-cutoff-fee",
  "candidate_for_standard": true
}

Расширение только добавляет поля и никогда не переопределяет стандартные (PROFILE-007). Флаг candidate_for_standard — заявка автора на включение в стандарт: такие расширения разбираются при подготовке следующей версии модуля.

Примеры

Оператор с глубоким прайсом, оператор с плоским прайсом из Excel и площадка.

aggregator-mpfit.json

{
  "fsp": "1.0",
  "party": {
    "role": "aggregator",
    "id": "mpfit",
    "name": "МПФИТ — биржа мощностей",
    "contact_channel": "https://mpfit.ru"
  },
  "modules": {
    "core": { "versions": ["1.0"] },
    "pricing": { "versions": ["1.0"], "level": "L3", "read_only": true },
    "capacity": { "versions": ["1.0"], "read_only": true },
    "quote": { "versions": ["1.0"] }
  },
  "endpoints": {
    "quote": "https://api.mpfit.ru/fsp/v1/quote"
  },
  "updated_at": "2026-07-25T10:00:00+03:00"
}

operator-excel-price.json

{
  "fsp": "1.0",
  "party": {
    "role": "operator",
    "id": "sklad-24",
    "name": "Склад 24",
    "contact_channel": "mailto:sales@sklad24.example"
  },
  "modules": {
    "core": { "versions": ["1.0"] },
    "pricing": { "versions": ["1.0"], "level": "L1" },
    "capacity": { "versions": ["1.0"] }
  },
  "updated_at": "2026-07-25T10:00:00+03:00"
}

operator-full-price.json

{
  "fsp": "1.0",
  "party": {
    "role": "operator",
    "id": "sklad",
    "name": "Складсервис",
    "contact_channel": "tg:@sklad_ff"
  },
  "modules": {
    "core": { "versions": ["1.0"] },
    "pricing": { "versions": ["1.0"], "level": "L3" },
    "capacity": { "versions": ["1.0"] }
  },
  "extensions": {
    "x-sklad:route-cutoff-fee": {
      "module": "pricing",
      "versions": ["1.0"],
      "spec": "https://sklad.example/fsp/route-cutoff-fee",
      "candidate_for_standard": true
    }
  },
  "endpoints": {
    "capacity": "https://sklad.example/fsp/capacity"
  },
  "updated_at": "2026-07-25T10:00:00+03:00"
}