06. Детерминированные ID (UUID v5)¶
Все TigerBeetle account ID и transfer ID в Payment Manager выводятся детерминированно из имени аккаунта / идентификатора intent через UUID v5 c фиксированными namespace'ами. Это даёт верифицируемость без чтения БД, идемпотентность повторных записей и возможность сверки с внешними системами.
Назначение¶
TigerBeetle оперирует 128-битными числовыми ID для аккаунтов и трансферов. Чтобы не хранить отдельный «реестр» и не плодить state, PM формирует эти ID детерминированно:
- На основе человекочитаемого ключа (имя аккаунта, либо
intentId:indexдля трансфера). - Через
uuidv5(input, namespace)получаем стабильный 128-битный UUID. - Преобразуем UUID в
bigint(hex без дефисов) → это и есть TB ID.
Формально:
Никакой случайности, никаких счётчиков — только чистая функция от входа.
Namespaces¶
Все namespace UUID объявлены как фиксированные константы в src/ledger/id-gen.ts и src/ledger/accounts.ts. Менять их нельзя (см. раздел ниже).
| Namespace константа | UUID v5 namespace | Где определён | Что генерирует | Формула (input для uuidv5) |
|---|---|---|---|---|
TB_NS |
3e7b4a1c-9f2d-5e8a-b6c3-4d1f07e2a9b5 |
src/ledger/accounts.ts |
TB account ID (128-bit) | accountName(type, opts) — см. ниже |
TRANSFER_NS |
7a9f3c1e-5b2d-4e8a-9f6c-3d1e07a2b5c4 |
src/ledger/id-gen.ts |
TB transfer ID (128-bit) | ${intentId}:${index} |
Имена аккаунтов (input для TB_NS)¶
accountName(type, opts) строит строку по строгим правилам в зависимости от типа аккаунта:
Тип аккаунта (AccountType) |
Шаблон имени | Пример |
|---|---|---|
USER_WALLET |
user.{userId}.{currency} |
user.1042.THB |
TRANSIT |
system.transit.{channel}.{currency} |
system.transit.IPPS.THB |
REVENUE |
system.revenue.{currency} |
system.revenue.THB |
NOSTRO |
system.nostro.{provider}.{currency} |
system.nostro.ipps.THB |
MERCHANT_WALLET |
merchant.{userId}.{currency} |
merchant.7.THB |
MERCHANT_SETTLEMENT |
merchant.{userId}.settlement.{currency} |
merchant.7.settlement.THB |
AGENT_WALLET |
agent.{userId}.{currency} |
agent.42.THB |
AGENT_SETTLEMENT |
agent.{userId}.settlement.{currency} |
agent.42.settlement.THB |
SERVICE_ACCOUNT |
service.{userId}.{currency} |
service.99.THB |
EQUITY |
system.equity.{currency} |
system.equity.THB |
Логика — см. функцию accountName в src/ledger/accounts.ts. Любые отступления от схемы приведут к другому UUID и, как следствие, к «новому» TB-аккаунту.
Transfer ID (input для TRANSFER_NS)¶
intentId— UUID интента (первичный ключ вpm.intent).index— порядковый номер трансфера внутри одного intent (0, 1, 2, …). У одного intent может быть несколько трансферов: hold, release, fee post, refund и т. д.
Зачем нужны deterministic IDs¶
1. Verifiability без state¶
Любой компонент системы, зная имя аккаунта (или пару intentId + index), может вычислить TB ID локально, без обращения к БД PM:
- Admin Panel запрашивает баланс конкретного мерчанта → вычисляет
accountId('merchant.7.THB')и идёт сразу в TigerBeetle. - Reconciliation-скрипт сверяет ledger с внешними отчётами PSP — ему достаточно знать
intentIdиindex.
Это резко снижает связанность: маппинг имя → ID хранится только в pm.tb_account_map, но не является источником истины — истина в формуле.
2. Idempotency повторных записей¶
TigerBeetle при попытке createAccounts / createTransfers с уже существующим ID возвращает статус exists (а не ошибку) и не дублирует запись. Это даёт встроенную идемпотентность на уровне ledger:
- Повторная попытка создать тот же intent (например, ретрай саги после краша воркера) → тот же
uuidv5TbTransfer(intentId, 0)→ TB вернётexists, баланс не сдвинется второй раз. - Повторный
seedAccounts()при старте PM → каждый системный аккаунт уже создан, TB вернётexists, всё ok.
Без детерминированных ID пришлось бы реализовывать собственный механизм idempotency-ключей поверх TB (и его согласованность с TB пришлось бы поддерживать вручную).
3. Reconciliation со стороны внешних систем¶
PSP-адаптеры и Admin Panel могут построить TB ID самостоятельно:
- Admin Panel читает TigerBeetle напрямую в read-only режиме (см.
docs/ADMIN_PANEL.md) — ему не нужен полный доступ к БД PM, достаточно знать имена. - Сверочные пайплайны (внутренний аудит, внешний аудитор) проверяют целостность: «вычислил ID из имени → нашёл аккаунт в TB → сравнил баланс с ожидаемым».
Связь с multi-entity accounts (merchant / agent / service)¶
Все «многосущностные» аккаунты подчиняются той же схеме именования и тем же namespaces:
merchant.{userId}.THB— основной merchant wallet.merchant.{userId}.settlement.THB— settlement-аккаунт для выплат.agent.{userId}.THB/agent.{userId}.settlement.THB— то же для агентов.service.{userId}.THB— служебные аккаунты (комиссии партнёров, технические балансы).
Это значит:
- Auth Center, регистрируя нового мерчанта, не «получает» TB account ID откуда-то — он знает формулу и может проверить существование без вызовов в PM.
- Новый мерчант с
userId = 99гарантированно получит TB account ID, выводимый изmerchant.99.THB. Никаких коллизий с уже существующими аккаунтами — UUID v5 коллизионно-устойчив на размерности 2^128.
См. также: feedback-account-resolution.md в memory (PM никогда не «угадывает» имя — Auth Center передаёт явно).
Запрет: НИКОГДА не менять namespaces¶
TB_NS и TRANSFER_NS — навсегда фиксированные значения. Любое изменение:
- Делает невычислимыми все ранее созданные TB account ID и transfer ID из их человекочитаемых ключей.
- Ломает все существующие записи в
pm.tb_account_map, в TigerBeetle, в логах, в outbox. - Ломает idempotency: повторная попытка интента создаст новый transfer ID → двойное списание.
- Ломает Admin Panel и любые внешние верификаторы.
Если когда-либо понадобится «версионирование» схемы ID — это делается через расширение input-строки (например, v2:user.1042.THB), а не через смену namespace. Но в текущей архитектуре такой потребности нет и не предполагается.
В createAccount есть страховочная проверка: id === 0n || id === 2n ** 128n - 1n → выбрасывается ошибка. Это защита от вырожденных входов, но не от смены namespace — последнее ловится только на ревью и при сверке с production-данными.
Связанные документы¶
../modules/ledger.md— детальный обзор ledger-модуля PM: createAccount, createTransfers, выбор кодов и флагов../05-tigerbeetle-accounts.md— типы TB-аккаунтов, коды, флаги (debits_must_not_exceed_credits,history), системные аккаунты, seed-процедура../04-two-phase-channels.md— где transfer ID используются в two-phase (pending → post/void) сценариях.