ADR 0001 — Формат нормализованного контракта GitProvider
Контекст
Фаза 2 (CLAUDE.md) требует нормализованного интерфейса GitProvider в
packages/shared, покрывающего 8 операций (createRepo, getRepo,
createBranch, protectBranch, commitFiles, listBranches,
openPullRequest, mergePullRequest), с типами, которые никогда не раскрывают
форму Octokit (GitHub) или REST-клиента GitLab (@gitbeaker),
и объектом capabilities, через который домен деградирует, когда операция
не поддерживается provider.
До этой сессии в apps/api уже был GitProvider — но как
abstract class, используемый как токен внедрения зависимостей NestJS,
с единственной операцией (createRepository), потребляемый уже работающим в продакшене
конвейером подготовки репозитория
(ProvisionRepositoryUseCase, GitProviderRegistry,
GitInfrastructureModule). Эта сессия ограничена лишь "фундаментом"
(типы + контрактная suite + полный LocalGitProvider) — доведение
GithubProvider/GitlabProvider до 8 операций остаётся на будущую
сессию.
Решение
Два контракта намеренно сосуществуют пока:
GitProvider(без изменений,apps/api/src/application/ports/git-provider.port.ts) — продолжает быть DI-токеном Nest, сcreateRepository. Ничто в нём не меняется в этой сессии;GithubProvider/GitlabProvider/registry/ use case подготовки не затрагиваются.GitProviderContract(новый,packages/shared/src/index.ts) — нормализованный интерфейс 8 операций +capabilities, типыGitRepo/GitBranch/GitPullRequest/GitCommitResult. Пока не привязан ни к одному DI-токену. Пока его реализует толькоLocalGitProvider(наряду с продолжением реализации старого, неизменногоGitProvider).
Назван GitProviderContract вместо повторного использования идентификатора
GitProvider — избегает конфликта имён в файле, реализующем оба
(LocalGitProvider), и совпадает со словарём "контрактной suite", который
сам CLAUDE.md уже использует для описания тестов.
Каждое поле идентификации репозитория использует externalId (не id
и не repoId) — то же имя, что уже используют CreateRepositoryResult/
ProvisionedRepository, чтобы быть согласованным с уже
сохраняемым.
capabilities: { protectBranch: boolean; pullRequests: boolean }
— два булевых флага, интроспектируемых во время выполнения
(provider.capabilities.protectBranch), без какой-либо гранулярности по операциям
помимо этого (напр.: нет "частичного protectBranch"). LocalGitProvider
объявляет оба как false — у локального bare repo нет платформы
для размещения защиты веток или pull requests. Когда вызывается операция,
охраняемая отсутствующей capability, provider бросает
GitNotSupportedError (никогда не сырой crash) — см. 0002.
Merge/PR в local: openPullRequest и mergePullRequest в
LocalGitProvider бросают GitNotSupportedError безусловно —
никакой имитации PR через ветку+прямой merge в этой сессии нет. Будущей
сессии bootstrap Gitflow, вероятно, понадобится операция прямого
merge (не "фальшивая PR") для providers без
pullRequests — эта операция здесь ещё не смоделирована, намеренно,
чтобы не выдумывать поверхность API, которую пока никто не потребляет.
Последствия
- Нулевой риск регрессии в уже работающем в продакшене конвейере
подготовки —
GitProvider/GithubProvider/GitlabProviderостаются ровно такими, какими были. - Явный долг: два контракта (
GitProviderиGitProviderContract) должны сойтись в будущей сессии, когдаGithubProvider/GitlabProviderтоже реализуютGitProviderContract, — в этот момент имеет смысл, чтобыGitProvider(старый) был выведен в пользу нового или чтобы один из них начал расширять другой. - Контрактная suite (
apps/api/test/contract/git-provider.contract.ts) уже написана переиспользуемо — ветвится поprovider.capabilities.*, чтобы решить правильную проверку, — поэтому её не нужно менять, когда войдут Github/Gitlab, меняется лишь harness, который её вызывает.
Обновление (Фаза 2, сессия 3 — bootstrap Gitflow)
"Явный долг" из предыдущего раздела оплачен: GithubProvider и
GitlabProvider уже полностью реализовывали GitProviderContract
со времён сессии учётных данных git (с проходящей подменённой контрактной suite
у 3 providers). В этой сессии GitProviderRegistry.get()
начал возвращать GitProviderContract вместо унаследованного GitProvider
— createRepository/CreateRepositoryInput/CreateRepositoryResult
были удалены (подтверждено grep: ProvisionRepositoryUseCase был
единственным потребителем). Контракт также получил 9-ю операцию,
getFileContent, нужную bootstrap для проверки "файл уже
закоммичен с тем же содержимым" перед повторным коммитом, — см.
docs/adr/0005-repo-bootstrap-idempotent-steps.md.