Текущий код, миграция и самопроверка проекта
Исторический проект
Эта страница фиксирует прежний проект, а не текущий статус выпуска. Подготовка ресурсов, scoped authorization и trusted MCP уже реализованы в коммитах Agentfy; см. контракт серверного чата и границы доказательств. Браузерные/BONSITE протоколы и более широкие предложения по оплате/жизненному циклу сохраняют свои условия выпуска. Боевой запуск партнёра не подразумевается.
Факты текущего исходного кода
Сокращённые пути находятся под api/src/slices/, если полный префикс api/ не указан; исходники прочитаны в checkout AGNT2-424. Требуемые изменения не являются уже работающим поведением.
| Текущее доказательство | Следствие и миграция |
|---|---|
api/src/slices/user/user/user.prisma: обязательный unique email, optional password, platformRole | Субъект независимо от credentials, nullable/неприменимый email внешних акторов без фиктивных адресов; уникальность прямых credentials сохраняется |
user/auth/domain/auth.types.ts: email обязателен в IAuthUser/JWT, union access/refresh/session | Явные scoped principal/source/audience, версионирование DTO/SDK; не расширять глобальный guard одной новой строкой |
user/auth/domain/auth.service.ts: захват passwordless stub по email, новая команда, вызов founding | Отдельный credential-only/link/bootstrap; не захватывать заглушки, не создавать лишнюю команду, исключить внешних акторов из founding |
user/auth/guards/jwtAuth.guard.ts: signature/type, sub/email в request | Проверка revocation state, явное разрешение endpoints для partner/linked; credentials/platform/financial-consent требуют прямого proof |
user/auth/domain/auth.service.ts: refresh проверяет type и наличие user, выдаёт новые токены | Persisted rotating families, source/epoch/version/reuse detection; сегодня такого отзыва нет |
user/team/domain/team.service.ts: раздельные team/key, seat и provisioning | Транзакционный allocation gateway + durable outbox; текущий метод не атомарен |
user/team/domain/teamProvision.port.ts: ошибки handlers логируются и подавляются | Хранить readiness/retry; успешный signup не гарантирует консьержа |
user/userTeam/userTeam.prisma, domain/userTeam.service.ts: unique user/team, несколько owners, count перед записью | Роль остаётся источником владения; lock/transaction для последнего owner/transfer и состояния неактивных субъектов |
agent/agent/agent.prisma: составной FK Agent(ownerId, teamId)→UserTeam(userId, teamId) | ID субъектов сохраняются; передать агентов либо сохранить неактивное членство и остановить jobs |
billing/{subscription,invoice,payment,paymentMethod}/*.prisma: team scope, invoice unique subscription/period, cascade команды | Timeline плательщика и retained invoice lines; не переносить старые подписки/историю, убрать риск удаления финансов перед новым биллингом |
billing/invoice/domain/invoice.service.ts, billing/payment/domain/payment.service.ts: invoice после completed payment | Текущий PayPal subscription flow не usage-invoicing; проверить мандаты, отмену и callbacks обоих режимов |
system/usage/meter/meter.prisma: frozen costUsdPico, ID без resource FK, rollups | Сохранить ledger/точность, добавить историческую коммерческую атрибуцию events/aggregates до смен плательщика |
Размещение по CleanSlice
Прочитаны MCP get-started, list-categories, выполнены запросы о cross-slice authentication/organization и transactions/idempotency/billing, прочитан docs/architecture/dependency-flow.md. Второй запрос не нашёл transaction guide: предложенная транзакция — наше решение, не выдуманное правило MCP. Дополнительно действуют слои проекта и constitution.
Контракты organization/identity/membership — singular slices user-группы; user auth владеет browser/link contexts. Billing L6 координирует provisioning, которому нужны user allocation L2 и payer state, через domain gateway с единой локальной транзакцией в data implementation. План обязан определить общий transaction boundary, не назвать набор независимых service calls атомарным. Agent L5 получает durable operation через зависимости вниз; внешние эффекты вне транзакции.
System usage L1 не импортирует user/billing. Верхние потребители передают неизменяемую атрибуцию через нижние domain ports/snapshots; устаревший snapshot проверяется до платного dispatch. Domain не зависит от Prisma, DTO/validation — presentation, persistence/mapping — data, cross-slice — aliases/barrels. Prisma sources и SDK меняются только при реализации, не в этой задаче.
Expand, backfill, gate, contract
| Фаза | Критерий перехода |
|---|---|
| 1. Расширение под выключенными flags | Таблицы org/external/link/session/operation/outbox/payer; совместимый direct auth; partner routes default deny |
| 2. Backfill | User/Team/Agent ID, владельцы, планы, платежи/подписки не меняются; текущие команды → Agentfy; только доказуемая атрибуция истории, неоднозначности в карантин |
| 3. Auth/allocation | No-email end-to-end, два proof, endpoint opt-in, версионированная lifecycle sync, atomic bootstrap/owner guards; все A-сценарии исполнимы |
| 4. Billing | Оба режима v1, реальный provider consent/cutover/reconciliation, retention, hard budget, спонсирование; платного пилота до этого нет |
| 5. Пилот | Test namespace, затем ограниченная production org; измеренные cancellation/lease, безопасная сериализация, EN/RU/UK UX и recovery |
| 6. Contract | Удаление legacy email assumptions только после старых клиентов/токенов и совместимости rollback readers |
Отдельные flags: provisioning, browser/link, paid billing. Rollback запрещает выдачу/платный dispatch, сохраняет субъектов, связи, tombstones, ledger и назначения; outbox/provider эффекты дренируются и сверяются идемпотентно. Возврат к бинарникам, не понимающим no-email actor, после их появления небезопасен: оставить совместимые readers либо roll forward. Нельзя откатывать объединением пользователей, удалением истории или повторной выдачей внешнего ID.
Нумерованный разбор атак и гонок
Это ожидаемые результаты проекта и будущие тесты, не отчёт о пройденных runtime-тестах.
| ID | Условия и действие | Ожидаемый результат | Точка обеспечения / будущий тест |
|---|---|---|---|
| A01 | Два bootstrap с одним ключом | Один actor/team/owner/assignment/operation | Unique receipt + transaction; concurrent HTTP test |
| A02 | Разные ключи/истёкшая receipt, тот же external team | Same fingerprint продолжает, изменение конфликтует; удалённый creator не возвращается | Permanent mapping; rotation/expiry/retry test |
| A03 | Сбой после commit до response/outbox | Та же операция, нет бесхозной платной команды/дубликата агента | Durable outbox/step idempotency; crash injection |
| A04 | Ключ BONSITE передаёт чужие org/team/agent | Нераскрывающий отказ без чтения/записи | Namespace по всей цепочке; routes/jobs/search/export suite |
| A05 | Злой issuer входит linked subject | Только его партнёрские права, нет direct/private/other-org и link metadata | Source-bound principal; все routes/serializers |
| A06 | Provision key вызывает browser issuance/payer accept | Отказ даже при известных ID | Scope + independent financial proof; negative API |
| A07 | Два direct аккаунта связывают один subject | Одна связь; без раскрытия второго аккаунта | Subject lock/unique; race test |
| A08 | Совпадает email invite/direct/partner | Нет auto-link, claim credentials, смены цели | Независимый proof/fixed target; forwarded invite |
| A09 | Чужой callback/code, неверный browser/PKCE | Нет сессии или подмены аккаунта | State/nonce/binding; login-CSRF/leak |
| A10 | Две вкладки redeem code, replay/expiry/cancel | Один победитель, без посторонней смены контекста | Atomic consume/tab context; browser race |
| A11 | Unlink, затем старый JWT/refresh/socket/download | Производный доступ закрыт; независимые сессии сохранены | Link epoch/family/proxy; stream/refresh |
| A12 | D→P→иная связь, refresh partner→direct | Нет объединения/повышения | One-hop/immutable source; graph/token tests |
| A13 | Два владельца одновременно снимают себя | Остаётся активный owner | Team lock/version; transaction race |
| A14 | Отзыв последнего external owner с живыми агентами | Отзыв успешен, команда/jobs остановлены, без auto-promote | Lifecycle/owner checks; live cancellation |
| A15 | Удаление владельца агентов или transfer без payer consent | Явная передача/остановка, payer прежний | FK/workflow; jobs/ownership |
| A16 | Cutoff, поздний usage и duplicate callback | Одна историческая атрибуция, без дубля денег/счёта | Timeline lock/occurredAt/dedup; boundary reorder |
| A17 | Два резерва последнего бюджета, provider timeout | Нет перерасхода/раннего release | Atomic reservation/reconciliation; fault test |
| A18 | Sponsor отозван при pending payer | Платная работа только при валидном покрытии; owner не платит сам | Revalidate activation; sponsor-removal |
| A19 | Subject удалён, приходит старое active событие | Нет активации или продления lease старой/повторной ревизией | Monotonic revision/tombstone; replay/reorder |
| A20 | Удалённая команда повторяет bootstrap | Tombstone/unavailable, не новая команда/сессия | Permanent ID/current state; delete/retry |
| A21 | BONSITE исчез/закрылся при team-paid | Работа org запрещена, direct/private доступны, нет auto-move | Status/subject lease; outage/closure |
| A22 | Скомпрометирован issuer, готовы коды/связи | Proof/code отозваны, links в карантине; direct credentials целы | Credential/epoch/provenance; incident interval |
| A23 | Прежний payer исключён, читает свой счёт | Invoice-only, без контента команды | Payer ACL отдельно; invoice auth |
| A24 | Credentials из partner flow на пустой установке | Нет лишней команды/агента/founder | Dedicated flow/founding exclusion; empty install |
| A25 | Delete direct vs unlink vs deprovision | Разные эффекты, другой actor/team/debt сохранён, grants не оживают | Lifecycle/relink integration |
| A26 | Test key/worker/guest token в production user API | Отказ даже при валидной подписи/известном ID | Environment/audience/type; token matrix |
| A27 | Owner transfer после изменения membership/policy | Отказ без частичного понижения | Proof/version; accept/revoke race |
| A28 | Нет state store или subject lease истёк | Fail closed, старое событие/heartbeat lease не продлевает | Action gate + fresh subject proof; outage/replay |
| A29 | Внешний owner приглашает свой direct account | Отказ; только внешние customer actors своего namespace | Target policy; deprovision bypass test |
| A30 | Org-paid A30+B70, team-paid C20, внутреннее спонсирование | Счёт org100, C20 отдельно, sponsorship не выручка; один direct payer не объединяет счета команд | Mode/team grouping key; mixed-payer invoice test |
Найденные и исправленные проблемы
Ранее режим оплаты был открытым вопросом: теперь оба обязательны в v1. Глобальный merge заменён стабильными субъектами. Provisioning отделён от browser issuance; явно признана способность issuer действовать как свои пользователи. Прямые приглашения в клиентские команды партнёра запрещены, иначе обходился deprovision. Последний owner не мешает отзыву по безопасности. Idempotency переживает ротацию/expiry без воскрешения ресурсов. Контакт, владение и финансовое согласие разделены; local allocation атомарен, внешняя готовность асинхронна. Дополнительно уточнены service initiator против owner, фиксация subject/code, quarantine после компрометации, lease отдельного subject и продажная стоимость бюджета.
Сроки, cutoff и lease — рекомендации, не новые подтверждённые требования пользователя. До выпуска обязательны: provider обоих режимов, измеренная отмена/lease, процедура проверки личности при recovery, retention/legal policy, проверенная совместимость протокола. Непройденный gate блокирует соответствующую возможность; нельзя спрятать безопасность за «потом». Перенос между организациями, защита от самого identity issuer и вложенные реселлеры вне v1; закрытие означает разрешённый экспорт/support, не автоматический перенос.
Walkthrough не обнаруживает противоречий инвариантов после исправлений. Проект готов к обсуждению дизайна с указанными release gates, не является доказательством безопасности или разрешением выпустить runtime.
Проверка документации
Проверить VitePress, локальные ссылки/якоря, navigation и паритет EN/RU/UK разделов/сценариев. Фактические результаты make check, app/admin builds и текущих HTTP startup checks записываются в PR. API/app не меняются; результатов новых runtime-тестов нет. Проследить три пути — прямой signup, external-only BONSITE, связь существующего direct — и A01–A30 по контрактам.