Skip to content

Формат id ​

Один формат для всех первичных id в Agentfy, Bonsite, Securify, Shiffty и Store Deputy (решение владельца 2026-10-15). Эта страница — эталон: остальные четыре продукта копируют отсюда модуль, тест-вектор и таблицу.

<prefix>-<adjective>-<noun>-<suffix>
user-calm-rivers-k8m4p2d7n9x3

Новые id выдаются только в каноническом виде. Прежний формат каждого продукта по-прежнему принимается для уже выданных id (LEGACY_ID_FORMATS), но никогда не генерируется. Исключение — AGNT2-588: organization-* меняется на org-* с сохранением всего хвоста; формат слов и суффикса не меняется.

Формат ​

ЧастьПравило
prefixПолное имя модели в kebab-case, но слово Organization сокращается до org (OrganizationMember → org-member, AgentMcpServer → agent-mcp-server), нижний регистр, до 32 символов, сам может содержать -. Реестр — ID_ENTITIES в api/src/slices/admin/idMigration/data/idInventory.ts.
adjectiveОдно слово из adjectives пакета human-id 4.1.3 — 200 слов.
nounОдно слово из nouns human-id 4.1.3 — 300 слов. Без verb/adverb, без своих словарей.
suffixРовно 12 символов Crockford base32 в нижнем регистре: 0123456789abcdefghjkmnpqrstvwxyz (без i, l, o, u). 60 бит.
случайностьКриптографическая: по умолчанию Node crypto.randomBytes. Слова — отбраковкой по маске, суффикс — customRandom из nanoid 3.3.11.
уникальностьПервичный ключ. При конфликте вся запись повторяется, не более 5 раз (withIdRetry). Слова в уникальность не входят.
длинаНе более 32 + 1 + 7 + 1 + 8 + 1 + 12 = 62 символов. Любое принимаемое значение, включая legacy, — не более 80 (ID_MAX_LENGTH).

Словари — массивы, которые экспортирует пакет, в порядке пакета, приведённые к нижнему регистру; слово с символом вне [a-z] при загрузке отбрасывается (в 4.1.3 таких нет). Зависимости закреплены точно, без ^: "human-id": "4.1.3", "nanoid": "3.3.11" (CommonJS).

Пример user-calm-river-… из общей задачи не является валидным id: слова river в human-id нет (есть rivers).

Генерация — точный алгоритм ​

Эталон, одинаковый байт в байт во всех продуктах (зафиксирован координатором по SHIF-28, Agentfy/shiffty#37). formatId(prefix, random = crypto.randomBytes) читает из источника random(n) (отдаёт n байт) по порядку:

  1. adjective — index(200)
  2. noun — index(300)
  3. suffix — customRandom(ID_SUFFIX_ALPHABET, 12, random)() из nanoid 3.3.11 с тем же источником

index(n) читает минимальное число целых байт, вмещающее n − 1 (big-endian), обрезает значение маской до битовой длины n − 1 и повторяет, пока значение не меньше n:

nбитбайт на попыткумаскапринимается, если
200 (adjectives)81255значение < 200
300 (nouns)92511(b0·256 + b1) & 511 < 300

customRandom nanoid для алфавита из 32 символов и длины 12 запрашивает ceil(1.6 × 31 × 12 / 32) = 19 байт одним вызовом, читает их с последнего к первому, берёт byte & 31 (все значения в диапазоне) и останавливается на 12 символах; первые 7 байт запроса не используются.

Контрольный тест-вектор ​

Источник отдаёт байты 0, 1, 2, 3, … по порядку (каждый вызов продолжает счёт):

formatId('user', counter) === 'user-afraid-swans-nmkjhgfedcba'

Байт 0 → adjectives[0] = afraid; байты 1, 2 → 258 & 511 = 258 → nouns[258] = swans; один запрос байт 3…21, прочитанных с 21 до 10 → символы 21…10 алфавита = nmkjhgfedcba. Читается ровно 22 байта. Эту строку проверяет набор тестов каждого продукта; другой результат означает другой словарь, порядок, алфавит или алгоритм — то есть неверную копию.

Проверка ​

isCanonicalId(value, prefix?) принимает ровно

^(<префиксы реестра, длинные первыми, экранированные>)-<adjective>-<noun>-[0-9abcdefghjkmnpqrstvwxyz]{12}$

с проверкой обоих слов по словарям (а не просто [a-z]+). prefix — один префикс, список или не указан (любой корректный префикс). Нормализации нет: верхний регистр, похожие Unicode-символы, percent-encoding и пробелы отвергаются. Число хвостовых сегментов фиксировано, поэтому префикс с - однозначен: team-api-key-… никогда не id team.

isLegacyId(value, prefix?) принимает LEGACY_ID_FORMATS продукта. isId(value, prefix) — то, что используют валидаторы DTO (@IsEntityId), pipe и gateway, — это isCanonicalId || isLegacyId. Проверка ничего не ищет и доступа не даёт.

Не канонический вид (тест в каждом продукте): третье слово (verb), суффикс из 6 или 20 символов, hex-суффикс другой длины, i/l/o/u в суффиксе, слово вне словарей — если значение не совпадает с legacy-форматом продукта. 12 hex-символов — подмножество алфавита Crockford, поэтому …-a1b2c3d4e5f6 является каноническим; различить это нельзя и не нужно.

Модуль ​

api/src/slices/setup/id/domain/idFormat.ts — одинаковый во всех пяти репозиториях, кроме LEGACY_ID_FORMATS. Экспорты:

Экспорт
ID_SUFFIX_ALPHABET, ID_SUFFIX_LENGTH = 12суффикс
ID_WORDS { adjectives, nouns }словари после загрузки
formatId(prefix, random?)единственный способ создать новый id
isCanonicalId(value, prefix?), isLegacyId(value, prefix?), isId(value, prefix)проверка
LEGACY_ID_FORMATS, legacyIdFormat(value, prefix?)прежние форматы продукта
assertIdPrefix, idPrefixOf, idPattern, ID_MAX_LENGTHпроверка префикса, шаблон OpenAPI

Реестр префиксов остаётся там, где он у продукта. Чистый TypeScript (без enum и parameter properties), поэтому Node загружает его напрямую.

Прежние форматы по продуктам ​

ПродуктLEGACY_ID_FORMATS (принимается, не генерируется)
Agentfy<prefix>-<adjective>-<noun>-<verb>-<20 Crockford> — adjective/noun/verb из human-id 4.1.3, записи на dev после AGNT2-535/537; голый UUID в нижнем регистре — исторические записи, которые ещё переводит окно AGNT2-535
Bonsite3 слова + 20 Crockford
Securify2 слова + 12 hex (неотличимо от канонического — см. выше)
Shiffty2 слова из своего словаря + 6 Crockford
Store Deputy2 слова + 12 [a-z0-9]

Agentfy: окно перевода ​

Исходное окно выполнено на dev в AGNT2-580. Решение владельца AGNT2-588 (2026-10-24) заменяет часть правила AGNT2-537: Organization, OrganizationMember, OrganizationCredential и OrganizationPolicy получают префиксы org, org-member, org-credential и org-policy. OrganizationAudit остаётся техническим UUID.

Одна команда api/scripts/id-migrate.ts работает и на legacy-базе (сразу выдаёт текущие префиксы), и после прежней миграции (заменяет только organization- на org-, сохраняя весь хвост). Платформенный ID — org-silver-days-zxy8s9bpp6hp. Старые HTTP-адреса отвечают not found, без алиасов. Исторические ссылки провайдеров разрешаются по цепочке записанных переходов.

Перед запуском нужны остановка писателей, зашифрованный backup и проверка восстановления. В одной транзакции обновляются ссылки, привязки шифрования и значения по умолчанию, отзываются сессии и подписанные контексты. Повтор меняет 0 ID. Сид существующей локальной базы требует сначала явной миграции и не создаёт дубли. История переходов, аудит и непрозрачные пользовательские данные не подвергаются глобальной замене строк. Процедура — specs/AGNT2-535-id/transition.md; фактические результаты — в отчёте AGNT2-588.