Хакатон Space Payments, задача 1. Вход — OpenAPI 3.0/3.1 спецификация провайдера выплат (YAML или JSON). Выход — готовый Ruby-сервис по контракту
Provider::BaseService, гайд интеграции, тестовые фикстуры, RSpec-тесты сервиса, мок-сервер провайдера и отчёт о том, что и с какой уверенностью распознано. Без нейросетей: правила, словари, детерминизм.
Зачем читать: это вход для жюри и экспертов. Пять минут — и вы знаете, как запустить, что получается, где в коде каждый критерий и что мы сознательно не делаем.
Статус: этапы M1–M4 закрыты, M5 (Docker, CI, README) собран.
rake ciзелёный за ~50 с (rubocop, 325 тестов, покрытие ≥ 97 %, generate всех примеров и их сгенерированные RSpec, детерминизм, фаззинг, лицензии).bin/e2eдоводит выплату доapprovedна сгенерированном моке для NovaPay, CardPay, SwiftPay (подписьt=…,v1=…), Райффайзена и OAuth2-провайдера (spec/fixtures/oauth2_payout.yaml). 20 реальных API (Stripe, Adyen, PayPal, Wise, Mollie…) анализируются без падений, отчёты —examples/real/reports/; PayPal, Velo, Dwolla и Open Banking получают токен OAuth2 client_credentials. Живой веб-интерфейс — http://forge.savvax.com.
Что происходит в записи (фаза за фазой):
- Команды —
bin/forge version,bin/forge help:analyze,generate,mock, флаги. - Анализ —
bin/forge analyze --spec examples/specs/novapay.yaml: 5 эндпоинтов с ролями и confidence, auth, маппинг статусов, действия по ошибкам, webhook с подписью, единицы суммы; 3 WARN с подсказками дляoverrides.yml. - JSON-отчёт —
--format json: тот же анализ для CI и веб-интерфейса. - Генерация командой из ТЗ —
bin/integrate --spec provider_api.yaml --provider novapay --lang ruby: прогресс-вывод, 7 файлов вoutput/novapay/,ruby -cи rspec сгенерированного кода прямо в процессе. - Гайд —
INTEGRATION.md: кратко, поток вызовов контракта, все ключи настройки, методы с примерами, реквизиты, webhook. - Фикстуры —
fixtures.json: запрос и ответы create, статус, webhook сexpected_operation_status. - Сгенерированный RSpec — 15 примеров на WebMock и фикстурах: доказательство, что сервис работает.
- Мок-сервер —
bin/forge mock:POST /payoutsиз фикстуры,GETстатуса,POST /_simulate/<id>/<event>,/_state. - e2e —
bin/e2e: мок-сервер из той же спеки,create_request → fetch_status → подписанный webhook → approved. - e2e на других схемах — SwiftPay (подпись
t=…,v1=…) и OAuth2 client_credentials (токен выдаёт мок): оба approved. - Overrides — CardPay с
examples/overrides/cardpay.ymlи--strict: все WARN закрыты, exit 0. - Честные ошибки — циклический
$ref: exit 1 с JSON-pointer и подсказкой; pay-in спека Kaspi (examples/payin/kaspi.yaml) без выплат: exit 2 с подсказкой про overrides. - Реальная спека — Stripe (594 эндпоинта) с
--include-paths: те же правила, никакой привязки к провайдеру.
Перезаписать: rake demo:gif (нужен vhs; сценарий — docs/demo.tape).
Без Docker (Ruby ≥ 3.3):
bundle install
bin/forge analyze --spec examples/specs/novapay.yaml
bin/forge generate --spec examples/specs/novapay.yaml --out tmp/out/novapay --force
bundle exec rspec -I lib -I tmp/out/novapay tmp/out/novapay/novapay_service_spec.rbКоманда из условия задачи (обёртка над generate, вывод в ./output/):
bin/integrate --spec provider_api.yaml --provider novapay --lang rubyDocker:
docker build -t forge .
docker run --rm -v "$PWD/examples:/app/examples" -v "$PWD/output:/app/output" forge \
generate --spec examples/specs/novapay.yaml --out output/novapay --forceПолная проверка (то же, что CI): bundle exec rake ci. Быстрая: bundle exec rake check.
Вывод bin/forge generate --spec examples/specs/novapay.yaml --out output/novapay --force
Parsing spec... ok (openapi 3.0.3, NovaPay Payout API 1.0.0)
Found 5 endpoints: POST /payouts, GET /payouts/{payout_id}, POST /payouts/{payout_id}/cancel,
POST /webhooks/payout, GET /balance
create POST /payouts createPayout confidence 0.95
status GET /payouts/{payout_id} getPayoutStatus confidence 0.90
cancel POST /payouts/{payout_id}/cancel cancelPayout confidence 0.95 (outside contract → cancel_request)
webhook POST /webhooks/payout payoutWebhook confidence 0.90
balance GET /balance getBalance confidence 0.85 (outside contract → fetch_balance)
Auth: ApiKeyAuth (api_key, header: X-API-Key) → credentials.api_key
Statuses (status): pending, processing → in_progress; completed → approved; failed, cancelled → rejected
Errors: 400 validation_error → reject; 401 (create) unauthorized → alert_block;
401 (status) unauthorized → alert_block; 402 insufficient_balance → retry;
404 not_found → reject; 409 (create) duplicate → treat_as_success;
409 (cancel) invalid_status → reject; 422 validation_error → reject;
429 rate_limit_exceeded → retry_backoff (Retry-After); 500 internal_error → retry
Webhook signature: X-NovaPay-Signature (HMAC-SHA256, raw body, hex) → credentials.callback_secret
Webhook events: payout.completed → approved; payout.failed → rejected; payout.processing → in_progress; payout.cancelled → rejected
Amount: integer, minor units (×100), min 1000 RUB — source: amount (integer, min 100000) → minor units: 'Сумма в копейках'
Fields: 8 request fields, 0 unmapped (recipient: sbp, card)
Generating service...
Generating integration guide...
Generating test fixtures...
Generating service spec...
Generating extras...
Generating mock server...
Verifying generated code... ok (ruby -c ×4, rspec 15 examples, 0 failures)
Output:
./output/novapay/novapay_service.rb
./output/novapay/INTEGRATION.md
./output/novapay/fixtures.json
./output/novapay/novapay_service_spec.rb
./output/novapay/novapay_extras.rb
./output/novapay/mock_server.rb
./output/novapay/report.txt
Warnings (3):
WARN signature_encoding_assumed X-NovaPay-Signature: encoding not stated; hex assumed
hint: webhook.signature_encoding: hex|base64 (overrides.yml)
WARN conditional_required recipient.bank_code: required only for type=sbp (from description)
hint: fields.recipient.bank_code.required_if: { field: type, equals: sbp } applied; verify
WARN conditional_required recipient.card_number: required only for type=card (from description)
hint: fields.recipient.card_number.required_if: { field: type, equals: card } applied; verify
Info (3):
INFO outside_contract POST /payouts/{payout_id}/cancel (cancelPayout) — generated as `cancel_request` helper
INFO outside_contract GET /balance (getBalance) — generated as `fetch_balance` helper
INFO duplicate_as_success HTTP 409 returns the success schema (PayoutResponse); treated as success
hint: the service reads the payout from the body
Done: 7 files, 3 warnings, 0 unsupported. Exit 0.
output/novapay/
├── novapay_service.rb # Provider::NovapayService < BaseService: ровно четыре метода контракта
├── novapay_extras.rb # Provider::NovapayExtras < NovapayService: cancel_request, fetch_balance (вне контракта)
├── novapay_service_spec.rb # RSpec на WebMock и fixtures.json — доказательство, что сервис работает
├── generated_spec_helper.rb # учётные данные, подпись, парсинг тел для сгенерированного RSpec
├── INTEGRATION.md # исчерпывающий гайд: кратко, поток контракта, все ключи настройки, методы с примерами,
│ # сумма, реквизиты, проверки, приём webhook и подпись, ошибки, файлы, ДОПУЩЕНИЯ
├── fixtures.json # примеры запросов/ответов/уведомлений и ожидаемые статусы операции
├── mock_server.rb # Sinatra-мок провайдера из той же спеки (demo и e2e)
└── report.txt # что распознано, confidence, WARN / UNSUPPORTED с подсказками
Реальный вывод bin/forge analyze --spec examples/specs/novapay.yaml (первые строки — дословно как в ТЗ; снапшот spec/snapshots/novapay_analyze.txt):
Parsing spec... ok (openapi 3.0.3, NovaPay Payout API 1.0.0)
Found 5 endpoints: POST /payouts, GET /payouts/{payout_id}, POST /payouts/{payout_id}/cancel,
POST /webhooks/payout, GET /balance
create POST /payouts createPayout confidence 0.95
status GET /payouts/{payout_id} getPayoutStatus confidence 0.90
cancel POST /payouts/{payout_id}/cancel cancelPayout confidence 0.95 (outside contract → cancel_request)
webhook POST /webhooks/payout payoutWebhook confidence 0.90
balance GET /balance getBalance confidence 0.85 (outside contract → fetch_balance)
Auth: ApiKeyAuth (api_key, header: X-API-Key) → credentials.api_key
Statuses (status): pending, processing → in_progress; completed → approved; failed, cancelled → rejected
Errors: 400 validation_error → reject; 401 (create) unauthorized → alert_block;
401 (status) unauthorized → alert_block; 402 insufficient_balance → retry;
404 not_found → reject; 409 (create) duplicate → treat_as_success;
409 (cancel) invalid_status → reject; 422 validation_error → reject;
429 rate_limit_exceeded → retry_backoff (Retry-After); 500 internal_error → retry
Webhook signature: X-NovaPay-Signature (HMAC-SHA256, raw body, hex) → credentials.callback_secret
Webhook events: payout.completed → approved; payout.failed → rejected; payout.processing → in_progress; payout.cancelled → rejected
Amount: integer, minor units (×100), min 1000 RUB — source: amount (integer, min 100000) → minor units: 'Сумма в копейках'
Fields: 8 request fields, 0 unmapped (recipient: sbp, card)
Warnings (3):
WARN signature_encoding_assumed X-NovaPay-Signature: encoding not stated; hex assumed
hint: webhook.signature_encoding: hex|base64 (overrides.yml)
WARN conditional_required recipient.bank_code: required only for type=sbp (from description)
hint: fields.recipient.bank_code.required_if: { field: type, equals: sbp } applied; verify
WARN conditional_required recipient.card_number: required only for type=card (from description)
hint: fields.recipient.card_number.required_if: { field: type, equals: card } applied; verify
Info (3):
INFO outside_contract POST /payouts/{payout_id}/cancel (cancelPayout) — generated as `cancel_request` helper
INFO outside_contract GET /balance (getBalance) — generated as `fetch_balance` helper
INFO duplicate_as_success HTTP 409 returns the success schema (PayoutResponse); treated as success
hint: the service reads the payout from the body
Done: 3 warnings, 0 unsupported. Exit 0.
Формат — docs/OUTPUT_FORMAT.md § 6.
CLI — основной интерфейс (по условиям задачи). Веб-слой — тонкая обёртка над теми же классами для демо жюри: загрузить спеку и overrides, увидеть отчёт, открыть/скачать 7 файлов, запустить сгенерированный RSpec и e2e (мок + webhook) кнопкой. Без базы и без новых гемов (Sinatra + Puma уже в Gemfile).
Развёрнутый экземпляр для жюри: http://forge.savvax.com — демо-стенд по HTTP без TLS, поэтому загружайте туда только открытые спецификации; лимит 20 МБ на спеку, прогоны хранятся на сервере.
bin/forge-web # http://localhost:8080 (PORT, FORGE_WORKDIR)
docker compose up --build # то же в контейнере, данные прогонов — в volume forge-data
docker build --target web -t forge-web . && docker run --rm -p 8080:8080 forge-webДеплой на сервер: любой хост с Docker — docker compose up -d; за reverse-proxy (nginx/Caddy) на 8080.
Прогоны хранятся на диске (FORGE_WORKDIR), секретов в них нет: credentials в сгенерированном коде —
плейсхолдеры. Ограничение размера спеки — 20 МБ. Forge::Error показывается с pointer и hint; любая другая ошибка —
страницей с текстом, а не «Internal Server Error» (см. фаззинг ниже). Кнопки «rspec» и «e2e» на странице прогона
запускают сгенерированный RSpec и e2e через мок; все файлы открываются и скачиваются, есть .tar и report.json.
rules/*.yml overrides.yml
│ │
provider_api.yaml ─▶ Load ─▶ IR ─▶ Analyze ─▶ Plan ─▶ Render ─▶ Verify ─▶ Report
│ │ │ │ │
SpecError Findings IntegrationPlan files ruby -c / rspec
| Стадия | Что делает | Где |
|---|---|---|
| Load | YAML/JSON → hash, проверка openapi: 3.x, резолв локальных $ref, проверка формы документа (контейнеры там, где их ждут анализаторы), ошибки с JSON-pointer и подсказкой |
lib/forge/loader.rb, ref_resolver.rb, shape.rb |
| IR | неизменяемые Data.define: Spec, Endpoint, Schema… — ничего не знает о платежах |
lib/forge/ir/ |
| Analyze | 7 анализаторов (роли эндпоинтов, auth, статусы, ошибки, webhook, единицы суммы, поля) → Finding(value, confidence, source, warnings) |
lib/forge/analyzers/, словари rules/*.yml |
| Plan | findings → IntegrationPlan; наложение overrides.yml; валидации из схемы; фикстуры |
lib/forge/plan/ |
| Render | ERB-шаблоны получают только план, никогда сырой OpenAPI | lib/forge/renderers/, templates/*.erb |
| Verify | ruby -c + запуск сгенерированного spec |
lib/forge/verifier.rb |
| Report | текст как в ТЗ + WARN / UNSUPPORTED / INFO с подсказками; --format json |
lib/forge/report.rb |
Ключевые принципы:
- Знание о провайдере не живёт в коде. Всё специфичное — в
rules/*.yml(синонимы статусов, веса сигналов ролей, алиасы полей, маркеры единиц суммы) или в пользовательскомoverrides.yml.rake guard:vendorпадает, если вlib/встречается имя провайдера. - Неоднозначное не угадываем молча. Из структуры спеки — автоматически. То, что лежит текстом в
description(единицы суммы, условная обязательность, кодировка подписи), — WARN в отчёте,# TODO(forge)в коде, раздел «Допущения» вINTEGRATION.mdи ключ вoverrides.yml. Подход подтверждён организаторами письменно на сессии вопросов и ответов. - Детерминизм. Одинаковый вход → байт-в-байт одинаковый выход (
rake determinism). Никаких сетевых вызовов и LLM во время генерации. - Строгий режим не прерывает работу.
--strictгенерирует все файлы и печатает полный отчёт, а ненулевой код (4) возвращает только в конце, если остались WARN/UNSUPPORTED — удобно для CI, где «допущение без overrides» должно быть красным, но артефакты всё равно нужны. - Падаем только когда генерировать нечего (нет create-эндпоинта → exit 2 с подсказкой, как указать его в overrides). Всё остальное — WARN/UNSUPPORTED, а не молчание и не крэш.
- Любой файл пользователя — не крэш. Битая структура (
responses: nope,parameters: {…},$ref: 5, YAML-теги!ruby/…, вложенность в тысячи уровней) →SpecErrorс pointer (exit 1). Всё, чего конвейер не ожидал, →internal error: …с подсказкой (exit 2, стектрейс только с--debug); веб показывает то же сообщение вместо «Internal Server Error». Проверено фаззингом в CI (rake fuzz): 159 ручных враждебных случаев, ~900 случайных структурных мутаций спек и overrides, ~250 враждебных запросов к мокам на каждый прогон — 0 падений.
# overrides.yml — общий механизм, не привязка к провайдеру
amount:
unit: minor # minor (копейки) | major (рубли); multiplier, minimum_major
statuses:
ON_HOLD: in_progress # статус провайдера → статус Space Payments
fields:
recipient.bank_code:
required_if: { field: type, equals: sbp }
destination.card.expiry: # поле без источника → своё выражение на стороне operation
source: "format('%02d/%02d', operation.payout_requisite.dig('card', 'expiry_month'), operation.payout_requisite.dig('card', 'expiry_year') % 100)"
webhook:
signature_encoding: hex # hex | base64
signature_payload: raw_body # raw_body | fields
signature_scheme: timestamped # plain | timestamped (t=<ts>,v1=<hmac>, HMAC над "<t>.<raw body>")
endpoints:
createTransfer: create # роль эндпоинта, если эвристика ошиблась
paths:
include: ['/v1/payouts*'] # ограничить анализ большой спекиbin/forge generate --spec … --overrides overrides.yml. Каждое применённое переопределение
попадает в отчёт как INFO override_applied. Полная схема — docs/RULES.md § 9, примеры с
комментариями — examples/overrides/.
| Спека | Что отличается от NovaPay | Результат без overrides | С overrides |
|---|---|---|---|
examples/specs/novapay.yaml (ТЗ) |
эталон | 3 WARN, 3 INFO, exit 0; e2e → approved | 0 WARN (examples/overrides/novapay.yml) |
examples/specs/cardpay.yaml |
bearer, сумма строкой в рублях, статусы NEW/SUCCESS/DECLINED/ON_HOLD в поле state, обёртка data, webhook через callbacks, HMAC-SHA512 base64, нет отмены |
5 WARN, 4 INFO | 0 WARN |
examples/specs/swiftpay.json |
OpenAPI 3.1 JSON, basic auth + oauth2, oneOf получателя, внешний $ref, подпись t=…,v1=… с timestamp, problem+json, top-level webhooks |
5 WARN, 2 UNSUPPORTED, exit 0; e2e → approved | 0 WARN (2 UNSUPPORTED) |
examples/specs/raiffeisen.yaml (реальная спека Райффайзенбанка, СБП) |
OpenAPI 3.0 на русском, bearer в тексте, контейнер payoutParams, тип payoutMethod: SBP, статус в объекте status.value, x-webhooks + x-examples, подпись описана текстом |
4 WARN, exit 0; e2e → approved | 0 WARN |
Все четыре покрыты golden-тестами байт-в-байт (spec/golden/, с overrides и без); e2e для всех четырёх и для
OAuth2-провайдера — rake e2e.
Реальные спецификации (Stripe, Adyen Payout и Transfers, PayPal Payouts, Paystack, Square, Plaid):
bundle exec rake real скачивает их и прогоняет analyze; отчёты — examples/real/reports/.
Ожидания и найденные ограничения — docs/REAL_SPECS.md. WARN на чужой спеке — это честность
инструмента, а не сбой.
rake real скачивает двадцать открытых спек (7 первой волны в таблице ниже и 13 второй) (examples/real/, в git не попадают), прогоняет analyze и
сравнивает отчёты со снапшотами examples/real/reports/*.txt. Ни одна не роняет инструмент; WARN — это
честность, а не сбой: каждый закрывается строкой в overrides.yml.
| Провайдер | Что распознано автоматически | Что требует overrides / ручного кода | Отчёт |
|---|---|---|---|
| Adyen Payout v68 | create POST /payout, basic auth (apiKey — альтернатива), сумма amount.value в minor units |
нет status-эндпоинта и webhook (WARN); поля с большой вложенностью | adyen_payout.txt |
| Adyen Transfers v4 | create/status, apiKey в query (WARN), 20+ статусов из enum по словарю | 100+ редких статусов → statuses.<X> (отчёт сворачивает список) |
adyen_transfers.txt |
| PayPal Payouts | create/status/cancel, OAuth2 client_credentials: токен по POST /v1/oauth2/token, затем Bearer |
batch items[] — массивы не мапятся (WARN) |
paypal_payouts.txt |
| Paystack | --include-paths /transfer*: create transfer_initiate, status, balance; $ref на path-pointer с ~1 и %7B |
конфликт status/verify и DELETE recipient как cancel → endpoints.* |
paystack.txt |
| Stripe (8 МБ, 594 эндпоинта) | --include-paths /v1/payouts*: create/status/cancel, статусы из description, сумма в cents, form-urlencoded тело (WARN); анализ 0.4 с |
webhook в спеке нет | stripe.txt |
| Square | статус-эндпоинт; create нет → WARN no_create_endpoint (generate → exit 2 с подсказкой); битые $ref вне контракта → UNSUPPORTED |
— | square.txt |
| Plaid | --include-paths /transfer/*: create /transfer/create, status POST /transfer/get (id в теле), cancel; apiKey в заголовках |
40+ полей запроса без источника → overrides | plaid.txt |
Сводка: SUMMARY.md.
Вторая волна (13 спек, rake real скачивает и их): Velo, Increase, Mollie, Dwolla, Wise, Open Banking UK — выплаты,
сервисы генерируются, сгенерированные RSpec зелёные; NOWPayments, Klarna, PAYONE Link — честный no_create_endpoint
(exit 2); VTEX и Adyen Balance Platform — pay-in/конфигурация, create только с WARN low_confidence; Adyen Checkout —
pay-in (POST /payments), сервис генерируется, но это не выплаты;
GOV.UK Pay — Swagger 2.0, понятная ошибка. Таблица и ссылки — docs/REAL_SPECS.md § 1a. Этот прогон вскрыл и закрыл
9 дефектов генератора на «диких» спеках (ключи с точкой, пустое тело 201, минимум в один цент, Currency как имя
переменной, общий $ref-пример у create и status и др.).
Живые API: сервисы, сгенерированные из этих спек, проходят собственные RSpec (6 из 7 спек с create; Square —
честный exit 2) и отправляли запросы в настоящие sandbox Stripe, Paystack, PayPal и Adyen с неверным ключом:
реальные 401 → provider.invalid_credentials, 400 с неизвестным кодом → provider.unknown_error, сетевой
сбой → provider.unavailable. Этот прогон вскрыл и закрыл 10 дефектов генерации (docs/AUDIT.md § 5a).
| Критерий (жюри/эксперты) | Где в репозитории |
|---|---|
| Разбор спецификации: методы, параметры, auth, статусы, ошибки, webhook | lib/forge/analyzers/*.rb, rules/*.yml, bin/forge analyze, spec/analyzers/, spec/snapshots/ |
Сервис по контракту Provider::BaseService, запросы, статус, ошибки, уведомления, конфигурация |
templates/service.rb.erb, lib/provider/base_service.rb (контракт — docs/CONTRACT.md), output/<p>/<p>_service.rb, BASE_URL/credentials |
| Преобразование данных: статусы, поля, единицы, обязательность | rules/status_map.yml, rules/field_aliases.yml, rules/amount_units.yml, lib/forge/analyzers/fields.rb, .compact и required_if в шаблоне |
| Универсальность | четыре спеки + golden, 20 реальных API (examples/real/reports/), examples/overrides/, --templates-dir, rake guard:vendor, секция UNSUPPORTED |
| Документация и тестовые материалы | output/<p>/INTEGRATION.md (с «Допущениями»), fixtures.json, генерируемый *_service_spec.rb |
| Удобство и демонстрация | этот README, bin/integrate, коды выхода 0–4, ошибки с pointer + hint, bin/e2e, bin/demo |
| Качество реализации | шесть стадий по каталогам, rubocop 0, покрытие ≥ 97 %, обработка ошибок разбора/генерации (spec/fixtures/broken/, spec/cli_spec.rb, lib/forge/shape.rb), фаззинг (spec/fuzz/), CI |
| Дополнительные идеи | генерируемый RSpec как доказательство; мок-сервер из той же спеки + e2e create → webhook → approved (5 сценариев, включая OAuth2 и подпись с timestamp); отчёт с confidence; overrides как рекомендованный механизм; веб-интерфейс с кнопками rspec/e2e (http://forge.savvax.com); фаззинг в CI; прогон на 20 реальных API; детерминизм |
- Только OpenAPI 3.0/3.1 (Swagger 2.0 → понятная ошибка exit 1). Только выплаты (payout); pay-in — «что дальше».
- Внешние
$ref(other.yaml#/…,http…) и циклы: в схеме запроса create — ошибка exit 1, иначе UNSUPPORTED + заглушка{}. - OAuth2: генерируется только
client_credentials(tokenUrlиз спеки,client_id/client_secretв credentials, токен кэшируется без учётаexpires_in); другие flows → bearer сTODO. Подписьt=…,v1=…(Stripe-стиль) проверяется как HMAC над"<t>.<raw body>"(WARN: схема взята из описания); прочие timestamp/nonce-схемы →NotImplementedErrorвverify_signature!с пояснением. oneOfполучателя: по умолчанию первый вариант + WARN; выбор —fields.<path>.variant: <SchemaName>.- Form-urlencoded тела (Stripe): отправляются как
form:с плоскими ключамиparent[child]+ WARNmedia_type_form; Stripe-стиль вложенности совпадает, другие кодировки — проверить с провайдером. - Статус через
POSTс id в теле (Plaid/transfer/get) поддержан; статус через query-параметр — только если параметр назван id/code/reference. - Идентификаторы подключения в пути (
/client/{clientHashId}/wallet/{walletHashId}/…, Nium-стиль): в URL из operation подставляется только id выплаты, остальные{param}берутся изcredentials.<param>(WARNpath_params_from_credentials, ключи перечислены в INTEGRATION.md «Заполнить вручную»). - Примеры из спеки сверяются с её схемами (
INFO fixture_schema_mismatch), но берутся как есть. - Секреты (API-ключ, HMAC secret) в документации нет — генерируются как
credentials.*с пометкой для ручного заполнения.
- Ruby 3.3:
.mise.tomlв корне,mise install && bundle install. bundle exec rake check— lint + тесты + guard.bundle exec rake ci— всё, что делает CI.- Golden обновляются осознанно:
UPDATE_GOLDEN=1 bundle exec rspec spec/golden_spec.rb, затем diff. bundle exec rake fuzz— фаззинг: враждебные спеки/overrides/параметры формы против веб-приложения, случайные структурные мутации спек через конвейер (SEED=… ROUNDS=…), враждебные запросы к мокам. Красный = исключение внеForge::Errorили ответ 5xx. Корпус —spec/fuzz/corpus/*.yml.
Pay-in (депозиты) тем же пайплайном; Swagger 2.0 через конвертацию; batch-выплаты (массивы полей);
полная JSON-Schema-валидация фикстур (json_schemer, сейчас — встроенная проверка типов/required/enum);
интеграция с CI Space Payments как шаг «новый провайдер → PR с сервисом и тестами».
